Author Topic: 32F417 - a cunning way to reset two DMA pointers from an edge on a pin, etc?  (Read 4450 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...
I have the two DACs outputting waveforms. This is done by loading values from two RAM tables, using DMA. The DACs are both to be triggered from the same timer so they run in sync but they get their data from different tables, via DMA. The DMAs are configured to read from circular buffers, as is normal for repetitive waveform generation.

I don't yet know if one can use a single timer to trigger both DACs but it's not a huge deal if one can't because two identically configured timers should be for ever in sync.

The next job is to sync these two waveform generators to a reference input, so that a +ve edge arriving on an input pin resets both DMA source pointers to their starting addresses. Is this possible? It is obviously possible if using interrupts but I would prefer to avoid that, due to latency issues.

The reference input waveform's +ve edges will go (via a comparator, to detect zero crossings accurately) to a timer (TIM1) for the purpose of measuring its period. I have PE7-PE10 available as inputs. There will be a separate RTOS thread which will use this period count to calculate the appropriate values for the two tables feeding the DACs. Is it possible, on a +ve edge, to transfer the contents of TIM1 into a RAM location, using DMA, (and zero TIM1) from where this RTOS thread could pick it up? Again, all obviously possible if interrupting from the input pin.

I have been going through the RM but there are so many options and while some of it is obviously possible, I don't think other parts are.
Z80 Z180 Z280 Z8 S8 8031 8051 H8/300 H8/500 80x86 90S1200 32F417
 

Online SiliconWizard

  • Super Contributor
  • ***
  • Posts: 14470
  • Country: fr
So first question - yes absolutely: each DAC has a separate selectable trigger source (from 8 sources, 6 of them being timers). You can use the same trigger source for both, absolutely.

As to the DMA thing. First note is that I would personally use the double buffer mode rather than the circular buffer mode for streaming samples to DACs. Now it probably all depends on your exact use case - maybe you're storing a single period of signal in the buffers and just want to generate it continuously, in which case the circular mode would be appropriate.

Regarding automatically resetting DMA pointers upon a certain trigger (here an external input) without using interrupts, I honestly don't know. I have read the DMA section in the RM and I'm frankly not sure this is possible, but I may have missed it.

What would be your requirement in terms of latency? What's the sample rate?
 

Offline bson

  • Supporter
  • ****
  • Posts: 2270
  • Country: us
You can even run them in dual DAC mode - one trigger, one DMA channel stream feeding one of the DAC_DHR*D registers, for example DAC_DHR12RD.  This is a 32-bit register with two 16-bit halves, one for each channel; they're right-aligned, so the DAC will only use the low 12 bits.  If instead you use DAC_DHR12LD it will use the top 12 bits (useful if you have 16-bit samples).  This of course only makes sense if you have a dual waveform.  If you need to mix and match waveforms between the two channels it's probably preferable to use a separate DMA channel stream for each waveform.
 

Offline bson

  • Supporter
  • ****
  • Posts: 2270
  • Country: us
Instead of resetting the DMA source address, have pin edge stop the timer and generate an interrupt.   In the interrupt handler, update the DMA addresses, number of words to transfer (if you change waveforms), and release the timer.
 

Offline Cerebus

  • Super Contributor
  • ***
  • Posts: 10576
  • Country: gb
If you read the timer documentation very carefully (Is there any other way to read ST documentation?) you will find that you can set a timer up 'all ready to go' and then start it running for the first time from an external trigger so that it is synchronised to the external signal. Then you can either leave it free running, or use it a a sort of 'stretched one shot' to come around and wait for the start signal again.

I use it (in an STM32F411) in the first mode to synchronise a timer to a PPS input on a GPSDO to get initial fast synchronisation to GPS.

I know this is only part of the puzzle, but at least it's something. Doesn't help you with resetting the DMA, but perhaps helps with the start synchronisation to an external source.

Here's the relevant bit of the timer initialisation code.

Code: [Select]
    rccEnableTIM5(FALSE);
    rccResetTIM5();
    nvicEnableVector(STM32_TIM5_NUMBER, STM32_TIMCAP_TIM5_IRQ_PRIORITY);
   
                                                            // ======================= Configure timer 5 ==========================
    TIM5->CR1   = TIM_CR1_URS ;                              // Only overflow and underflow events generate an event interrupt.

    TIM5->CR2   = 0;
    TIM5->SMCR  = TIM_SMCR_SMS_2 | TIM_SMCR_SMS_1           // SMS(110) - trigger mode, start on TRGI
                | TIM_SMCR_TS_2 | TIM_SMCR_TS_0 ;           // TS(101) - TRGI is TI1FP1
                                                           
    [Irrelevant settings snipped.]

    // NOTE WELL
    // We do NOT enable the timer, that is done automatically by the first PPS pulse received.
Anybody got a syringe I can use to squeeze the magic smoke back into this?
 

Offline bson

  • Supporter
  • ****
  • Posts: 2270
  • Country: us
You might able to set up one DMA to trigger on an external pin, to do a transfer from memory to memory, without increment, with its destination memory being the address register of another DMA stream?  Not sure if that works, or if memory to memory truly has to be exactly that or if it can be something like a DMA register, given you will only do exactly aligned 32-bit to 32-bit.
 

Offline peter-hTopic starter

  • Super Contributor
  • ***
  • Posts: 3697
  • Country: gb
  • Doing electronics since the 1960s...
" I would personally use the double buffer mode rather than the circular buffer mode for streaming samples to DACs. Now it probably all depends on your exact use case - maybe you're storing a single period of signal in the buffers and just want to generate it continuously, in which case the circular mode would be appropriate."

I looked this up and it seems to be for usage where one wants to be dynamically changing the DMA addresses. I may well want that. The source buffer for DMA -> DAC will always be a fixed size e.g. 500 values, but it will be recomputed frequently - probably around every 100ms. I want to vary the frequency and amplitude of the waveform. The frequency can be done in two ways: change the timer driving the DAC, or change the DMA source table length. Both give similarly fine control; I will be generating around 400Hz and with say a 250 sample table that is 100kHz i.e. 10us between samples, and the timer (which will be driven from APB1 or APB2; both 42MHz in my case) which with a x1 prescaler gives a frequency control resolution of 1/420 of 10us, while varying the table length will give a resolution of 1/250. The timer value can be changed without messing up its current cycle AFAIK, and the DMA double buffer mode gives you the same thing for the source address (but seems a lot more complicated to configure).

It would be handy to be generating one table while the DMA is reading from another, and then switch the tables, preferably at the next zero crossing so there are no glitches. Again, this would be trivial with an interrupt from the DMA, with the ISR checking if the other table is ready for use.

Normally the output will be a sinewave, but the amplitude will vary, from +1 through zero to -1. For those who know, this is to emulate an LVDT. Of course the obvious way to do this is to have a four quadrant multiplying DAC, with the above mentioned reference waveform being its input, and with the digital input coming from software, but I want to do it all in the CPU.

For measurement of the input waveform period, I recall reading somewhere that one can config a DMA to be triggered from a pin and do a transfer from a timer to memory, but we also want to reset the timer, and if the timer is configured to reset from the pin also, does the timer get reset before or after the DMA transfer? :) Again, an ISR would make this more obviously controlled.
« Last Edit: November 11, 2021, 07:16:10 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
For measurement of the input waveform period, I recall reading somewhere that one can config a DMA to be triggered from a pin and do a transfer from a timer to memory, but we also want to reset the timer, and if the timer is configured to reset from the pin also, does the timer get reset before or after the DMA transfer? :)
You do input capture together with the reset. This in itself is described in the PWM input mode subchapter of the TIM chapter of RM (you want to read out the period only but the principle is the same).  Reset is delayed by the slave-mode controller by a couple of cycles (this is not specified by ST, putting it into the realm of "what's a clock or two amongst friends" type of handwaves) later so the counter's value is captured before the reset happens.

DMA part is, that you trigger the DMA from the given channel itself. The captured value stays there the whole period until the next edge causing capture. Btw., TIM1 has a priviledge to have individual DMA triggers from individual channels, see DMA2 request mapping table in DMA chapter in RM.

I personally don't t do the reset part; I simply capture, DMA to RAM buffer, and calculate period from difference.

Now for the "resetting DMA" part - doable, by triggering a timer (maybe through the internal master-slave link) which in turn would trigger two successive writes into the Control register of DMA stream which  feeds the DAC, which would toggle its enable bit. However, I personally wouldn't do this, it's too risky, as DMA disable needs a wait for the given channel to finish its job, and this is a nearly unspecifiable time, as it depends on bus conflicts etc. Read: it's potentially risky. [EDIT] I've realized that you'd need another DMA to clear the DMA status flags in between the two writes to control register; that'd mean another timer and another DMA, moving it firmer into the impractical corner. [/EDIT]With 168MHz system clock you have 1680 cycles between DAC sampled at 100kHz, with carefully crafted interrupt it's enough to reset the DMA between two consecutive DAC samples, and I don't see why would you need better latency for this. With 500Hz of input signal this would not present any significant processing load either.

Btw. I don't understand why would you want to ever reset the DMA, as that would create a discontinuity in your output signal; I've just commented on your original request.

JW
« Last Edit: November 11, 2021, 09:18:19 am by wek »
 

Offline peter-hTopic starter

  • Super Contributor
  • ***
  • Posts: 3697
  • Country: gb
  • Doing electronics since the 1960s...
Great points - many thanks.

Yes - no need to reset the counter used to measure the input waveform period. It would be TIM1, which I have kept reserved for this job because it has the most features. The 32 bit count will take ages to overflow and one could either handle the overflow correctly (like the other thread we had here, for the CYCCNT) or disregard the value if it has overflowed and go for the next one. I would still like to set up DMA to copy the counter value to RAM, on a zero crossing, because that will give me a "totally zero" latency.

Also no need to reset the two DMA source pointers on the zero crossing, because I can control the phase of the generated waveform (relative to the zero crossings of the input one) by generating the wave tables with an appropriate shift. So the two DMAs, stuffing the two DACs, can free-run across a constant RAM source area. The double buffer option will still be very useful.

I need to check that PE7-PE10 can trigger the DMA transfer from the TIM1 32 bit counter to a RAM address, on a +ve edge. It is probably limited to some DMA channels only. Currently I have all DMAs spare, though two channels will be used for the DACs. I find it very confusing to work out what can work with what.

If we aren't resetting the DMA source pointers, the actual phase locking needs to be done somehow, and one way is to feed the output waveform into a comparator and use a timer to measure the delay between that and the input waveform, and then have a feedback loop to zero that.
« Last Edit: November 11, 2021, 09:45:15 am by peter-h »
Z80 Z180 Z280 Z8 S8 8031 8051 H8/300 H8/500 80x86 90S1200 32F417
 

Offline Psi

  • Super Contributor
  • ***
  • Posts: 9946
  • Country: nz
You might able to set up one DMA to trigger on an external pin, to do a transfer from memory to memory, without increment, with its destination memory being the address register of another DMA stream?  Not sure if that works, or if memory to memory truly has to be exactly that or if it can be something like a DMA register, given you will only do exactly aligned 32-bit to 32-bit.

This. 

I've had to do that before, it seems a bit hacky to use DMA to set a hardware config register, but works and it sometimes the only way to do some things.
Greek letter 'Psi' (not Pounds per Square Inch)
 

Offline ozcar

  • Frequent Contributor
  • **
  • Posts: 322
  • Country: au
You might able to set up one DMA to trigger on an external pin, to do a transfer from memory to memory, without increment, with its destination memory being the address register of another DMA stream?  Not sure if that works, or if memory to memory truly has to be exactly that or if it can be something like a DMA register, given you will only do exactly aligned 32-bit to 32-bit.

I can't see that working. The Fine Manual says that the DMA stream memory address register can only be written when the stream is disabled, or for double buffer mode, if the other buffer is in use at the time.
 

Offline Psi

  • Super Contributor
  • ***
  • Posts: 9946
  • Country: nz
You might able to set up one DMA to trigger on an external pin, to do a transfer from memory to memory, without increment, with its destination memory being the address register of another DMA stream?  Not sure if that works, or if memory to memory truly has to be exactly that or if it can be something like a DMA register, given you will only do exactly aligned 32-bit to 32-bit.

I can't see that working. The Fine Manual says that the DMA stream memory address register can only be written when the stream is disabled, or for double buffer mode, if the other buffer is in use at the time.

So there's no single bit you can flip on the DMA control registers to cause it to restart and start again from zero?
(or timer register if you are using a timer output to set the DMA read address.)

In fact that is probably the way to do it, setup timer to control both DMA read address's then you can use another DMA to copy a 0 into the timer count register on GPIO trigger
« Last Edit: November 11, 2021, 10:21:13 am by Psi »
Greek letter 'Psi' (not Pounds per Square Inch)
 

Offline wek

  • Frequent Contributor
  • **
  • Posts: 495
  • Country: sk
It would be TIM1, which I have kept reserved for this job because it has the most features. The 32 bit count will take ages to overflow
TIM1 is a 16-bit counter.

TIM2 and TIM5 are 32-bit.

I need to check that PE7-PE10 can trigger the DMA transfer from the TIM1 32 bit counter to a RAM address, on a +ve edge.
As I've said, you want to capture *and* transfer the captured value.

It is probably limited to some DMA channels only.
The elements in DMA in 'F4 are called Streams. The individual input signals to one stream, selected as the particular trigger, are called channels. Read the DMA chapter in RM.

As I've said, TIM1 has individual triggers to different DMA streams, so you can individually capture and then trigger DMA transfer on any of the four TIM1 channels.


I find it very confusing to work out what can work with what.
The usual learning process, i.e. thoroughly reading the relevant chapters in RM, and performing small experiments to check the understanding, should help with this.

JW
 

Offline peter-hTopic starter

  • Super Contributor
  • ***
  • Posts: 3697
  • Country: gb
  • Doing electronics since the 1960s...
Googling around, everyone seems to be doing period measurement using CH1 of "some" timer, and this is TIM1 to whose pins I have access on my board:



and TIM1_CH1 is accessible on PE9, so feeding that with a comparator should work.

The Q is whether TIM1 can trigger DMA to store the value reached.

I think most people use the Cube IDE "graphical pin configurator / code generator" to find out what can go to where :) Does the RM spell that out anywhere?
« Last Edit: November 11, 2021, 04:20:25 pm by peter-h »
Z80 Z180 Z280 Z8 S8 8031 8051 H8/300 H8/500 80x86 90S1200 32F417
 

Offline bson

  • Supporter
  • ****
  • Posts: 2270
  • Country: us
the timer (which will be driven from APB1 or APB2; both 42MHz in my case) which with a x1 prescaler gives a frequency control resolution of 1/420 of 10us,
Note that if the APB bus clocks are divided down from HCLK (and not equal to HCLK) the APB timer clocks are twice the bus clocks.  So you get 1/840 of 10μs.  If HCLK is used straight up as an APB clock (/1), then that APB bus' timer clock will be HCLK as well.
« Last Edit: November 11, 2021, 04:37:17 pm by bson »
 

Offline bson

  • Supporter
  • ****
  • Posts: 2270
  • Country: us
I think most people use the Cube IDE "graphical pin configurator / code generator" to find out what can go to where :) Does the RM spell that out anywhere?
Section 10.3.3, "Channel Selection".

 

Offline peter-hTopic starter

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

I wonder if DMA is needed to retrieve the counter value at all. From the RM, page 538:

Input capture mode
In Input capture mode, the Capture/Compare registers (TIMx_CCRx) are used to latch the
value of the counter after a transition detected by the corresponding ICx signal.


So the value reached is latched in CCR anyway, from which it can be read out "at leisure".

And CH1 feeds through to IC1.

What am I missing?
Z80 Z180 Z280 Z8 S8 8031 8051 H8/300 H8/500 80x86 90S1200 32F417
 

Offline bson

  • Supporter
  • ****
  • Posts: 2270
  • Country: us
It's like anything else - you don't want to run interrupts at very high rates.  DMA is a lot cheaper for higher rate transfers, and even more so if your interrupt handler also has to manage buffer pointers and counters in memory somewhere.  The drawback of course is it's a planning puzzle to allocate DMA streams, or do it dynamically and accept there's DMA contention (can't say read an SPI device while a USART is transmitting).  Of course if there's contention you have to implement a dynamic assignment or queueing system.
 

Offline peter-hTopic starter

  • Super Contributor
  • ***
  • Posts: 3697
  • Country: gb
  • Doing electronics since the 1960s...
Another reason for not interrupting from the zero crossings is that noise on that pin will likely hang up the whole system - unless the ISR is written to disable further interrupts until some timer has expired, etc.
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...
Would anyone agree that feeding zero crossings into PE9 and using TIM1 should maintain a continuously latched value (of the waveform period) in CCR, as posted above
https://www.eevblog.com/forum/microcontrollers/32f417-a-cunning-way-to-reset-two-dma-pointers-from-an-edge-on-a-pin-etc/msg3806189/#msg3806189

I will prototype it when I get a chance but wonder if anyone can see a problem.
Z80 Z180 Z280 Z8 S8 8031 8051 H8/300 H8/500 80x86 90S1200 32F417
 

Offline wek

  • Frequent Contributor
  • **
  • Posts: 495
  • Country: sk
If given pin is set to AF in GPIO_MODER and the so is appropriate value for TIM1_CH1 in GPIO_AFR, and TIM1 clock is enabled in RCC and its CH1 is set to Input Capture from "it's own" pin in TIM1_CCMR1, then upon arrival of an edge (with polarity and "stability" given by respective fields in TIM1_CCER and TIM1_CCMR1), current value of TIM1_CNT is copied into TIM1_CCR1 and TIM1_SR.CC1IF is set (which in turn can trigger interrupt or DMA if respective CC1IE/CC1DE in TIM1_DIER is set).

The value in TIM1_CCR1 remains "continuously latched", until next such edge arrives.

JW
 

Offline peter-hTopic starter

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

I have no idea how I got the idea that something (an ISR, or a DMA writing to a fixed RAM address) needs to capture the timer value. It is completely unnecessary - so long as the RTOS gets around to it before the next cycle, and even then it doesn't matter if a few values are lost, in the context of frequency/period measurement of a slowly changing input waveform.

Basically, you set up the timer and you have a dead simple frequency meter :)

Syncing the two outputs to the input is a separate thing. I was thinking of a simple way of feeding one of them (they will always be in sync, by writing both DACs with a 32 bit value), and the input waveform (squared-up) to a XOR gate, lowpass filter that, feed to an ADC, and when the measured DC is at minimum, you are in sync. A slick way is to use another timer to measure the time delay.
« Last Edit: November 19, 2021, 10:44:44 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 am having a bit of trouble with setting up TIM1 and wonder if anyone can spot anything obvious. This is the code

Code: [Select]
// Set up TIM1 for ~400Hz waveform period measurement, via PE9
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.Mode = GPIO_MODE_INPUT;
GPIO_InitStruct.Pull = GPIO_PULLUP; // PE9 = pullup because comparator driving it is open drain o/p
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;
GPIO_InitStruct.Alternate = GPIO_AF1_TIM1;
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

TIM1->ARR = 0xffff; // auto reload (0 does not work, even if up-counting)

TIM1->PSC = 3; // prescaler=4, for 10.5MHz counter speed

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

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->CNT = 0;
TIM1->CR2 = 0; // TI1S=0 - TIM1_CH1 pin is connected to TI1 input
TIM1->RCR = 0;
TIM1->SMCR = 0; // slave mode disabled
TIM1->DIER = 0; // interrupts disabled

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

}

and this outputs varying CNT but 0 all the time for CCR1 (with a 400Hz 0 to +3.3V input on PE9)

Code: [Select]
// Loop for ever, yielding to RTOS when nothing to do
while (1)
{
uint16_t cnt = TIM1->CNT;
uint16_t ccr1 = TIM1->CCR1;
debug_thread_printf("===== TIM1 = %5u %5u", cnt, ccr1);

osDelay(1000);
}

TIM1 values, after stepping through above code, are



(except ARR=0xffff now)

Funnily enough, in my efforts to split up this project to get it done faster, I paid someone freelance to write this code, and I believe he had it all working, but he did it on some development board, so the code is of no use to me except for reference. He used TIM5 for this (which I can't access because of no available pins) and this was his code

Code: [Select]
void timer5_init(void)
{
RCC->APB1ENR |= (1u << 3); // TIM 5 clock enable
TIM5->CR1 &= 0xfffe; // disable TIM4
TIM5->CCMR1 = (3u << 12) // filter = 8
|(0u << 10) // prescale off
|(1u << 8); // CC2 channel is input mapped to T12
TIM5->CCMR2 = (3u << 4) // filter = 8
|(0u << 2) // prescale off
|1u; // CC3 channel is input mapped to T13
TIM5->CCER = (0u << 7) // CC2 rising edge capture
    |(0u << 6) // reserved keep at 0
|(0u << 5) // CC2 rising edge capture 2nd bit
|(1u << 4) // enable CC2 capture
|(0u << 11) // CC2 rising edge capture
|(0u << 10) // reserved keep at 0
|(0u << 9) // CC2 rising edge capture 2nd bit
|(1u << 8); // enable CC3 capture
TIM5->DIER = (1u << 10) // DMA enabled onCC2
|(1u << 11); // DMA enabled on CC3
TIM5->CNT = 0u;
TIM5->CR2 = 0u; // be sure CCDS = 0 so DMA requests on CC event

// set up GPIO so PA1 is TIM5 CH2 and PA2 is TIM5 CH3

GPIOA->MODER &= 0xffffffc3;
GPIOA->MODER |= 0x000a0028; // pins A1 and A2 to AF mode

GPIOA->AFR[0] &= 0xfffff00f; // set AF for TIM5 on both
GPIOA->AFR[0] |= 0x00000220;


tim5_dma_init();
}

Any tips much appreciated. I can confirm PE9 has a pullup now (earlier code sets it to a pulldown) so the pin config has clearly worked. There is also a HAL function for setting up the timer but it is quite complicated. It seems to be HAL_TIM_OC_Init and HAL_TIM_OC_ConfigChannel: to use the Timer to generate an  Output Compare signal. Of course I have googled for solutions; all online seem to use the HAL functions but as usual nobody posts how (or if) they solved it - typical online issue. I have worked through the timer section in the RM and through this: https://www.st.com/resource/en/application_note/dm00236305-generalpurpose-timer-cookbook-for-stm32-microcontrollers-stmicroelectronics.pdf

Post edited because I got the counter running, but not the compare feature.
« Last Edit: November 21, 2021, 10:06:43 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
TIMx_ARR must not be 0.

JW
 

Offline peter-hTopic starter

  • Super Contributor
  • ***
  • Posts: 3697
  • Country: gb
  • Doing electronics since the 1960s...
Yes - posts crossed - found that just this second :)

BTW PCLK1 & 2 are 42MHz, so a precaler of 3 (actually =4) should give a reasonable count with a 400Hz input.
« Last Edit: November 21, 2021, 10:19:23 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...
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