Skip to main content
RBack.1
Associate III
August 5, 2023
Question

inline assembly macro inconsistent

  • August 5, 2023
  • 14 replies
  • 5345 views

I am using the STM32F746VET6. I am aware that assembly instructions do not have a guaranteed in execution time, however I am surprised how consistently inconsistent my inline assembly macro is behaving. I'm really hoping there is just a mistake in the macro that I can fix. My IVT is in ITCM (memory address 0x0) and the ISR is also in ITCM (memory address 0x200). The 1st, 2nd, 4th, 5th, and 6th pin states show very close to the same delay every time. The 3rd and 7th pin states are always ~1.7x the others, however.

This is the macro:

 

 

#define nop_delay_def(delay_var) __asm volatile (".Lloop%=: \n\t" \
 " subs %[delay], #1 \n\t" \
 " bne.n .Lloop%= " \
 : \
 : [delay] "r" (delay_var))

 

 

And this is the ISR:

 

 

void __attribute__((optimize("O0"), section(".itcm_text"))) TIM1_TRG_COM_TIM11_IRQHandler(void) {

 PULSER1_GPIO_Port->ODR |= PULSER1_IN1_POS_Pin;
 nop_delay_def(60);
 PULSER1_GPIO_Port->ODR |= PULSER1_IN2_POS_Pin;
 nop_delay_def(60);
 PULSER1_GPIO_Port->ODR &= ~PULSER1_IN1_POS_Pin;
 nop_delay_def(60);
 PULSER1_GPIO_Port->ODR &= ~PULSER1_IN2_POS_Pin;
 nop_delay_def(60);
 PULSER1_GPIO_Port->ODR |= PULSER1_IN1_POS_Pin;
 nop_delay_def(60);
 PULSER1_GPIO_Port->ODR |= PULSER1_IN2_POS_Pin;
 nop_delay_def(60);
 PULSER1_GPIO_Port->ODR &= ~PULSER1_IN1_POS_Pin;
 nop_delay_def(60);
 PULSER1_GPIO_Port->ODR &= ~PULSER1_IN2_POS_Pin;
 HAL_IWDG_Refresh(&hiwdg); // kick the watch dog since FreeRTOS is suspended

 TIM11->SR = 0;
}// TIM1_TRG_COM_TIM11_IRQHandler

 

 

 I have attached a scope shot at the bottom. There is no jitter or variation in the pulses. They show that the macro is inconsistent consistently.

This topic has been closed for replies.

14 replies

Tesla DeLorean
Guru
August 5, 2023

Could you not put code fragments in startup.s where you can control the alignment, placement and interworking ?

Don't use RMW on the GPIO ODR, use the BSRR to change the state of bit(s) in a single write cycle

Tips, Buy me a coffee, or three.. PayPal Venmo (See Profile) Up vote any posts that you find helpful, it shows what's working..
RBack.1
RBack.1Author
Associate III
August 5, 2023

@Tesla DeLorean wrote:

Could you not put code fragments in startup.s where you can control the alignment, placement and interworking ?

Don't use RMW on the GPIO ODR, use the BSRR to change the state of bit(s) in a single write cycle


I'll look into putting code fragments in startup.s if that's a possible solution to this problem! I can look into BSRR as well. Thanks!

TDK
August 5, 2023

The Cortex-M7 core just doesn't make a guarantee you can use here. I don't see any issues with the macro. You could use DWT->CYCCNT to get within probably about 3 ticks of your target.

"If you feel a post has answered your question, please click ""Accept as Solution""."
RBack.1
RBack.1Author
Associate III
August 5, 2023

Thanks, I understood that there is no guarantee I just expected that to be a few ticks variation at worst rather than consistently 1.7x the execution time of the loop.Unfortunately my application needs to accurate down to one tick.

The obvious correct solution is to use an output compare instead of code to control the timing but that was complicated by the need to drive 15 pins in 7 states. We do think we may have come up with a solution to this using multiple timers and DMA but our board isn't currently laid out to use them.

The really frustrating thing is a previous version of code did test to have accurate timing down to one tick and we weren't even using an inline assembly macro at that time: we were calling an assembly function! It seems very small changes to the code result in very big changes in the timing, however, and we just got lucky with that previous version of code (for a whole year!).

KnarfB
Super User
August 6, 2023

If similar code used to be good enough before, check what else has changed in the system setup, like I and D caches?

Try unrolling the loop and make it branchless. More code, but more deterministic.

Try timer-driven DMA to GPIO register if those pins are concentrated on one or a few ports. No guarantee for perfect timing either, but it may be worth a try.

And, I definitely like the idea of @TDK, not using a fixed-length loop, but polling DWT->CYCCNT until the delay time is up.

hth

KnarfB

 

RBack.1
RBack.1Author
Associate III
August 6, 2023

We've been tearing our hair out trying to figure out what the difference is between the old and new code. We've disabled all of the advanced features we can disable in MX but our old code functioned as expected without doing that!

We have tried unrolling the loop but frustratingly we found that 20 asm nops in the first cycle and 30 asm nops in the second is what resulted in the pulses being the same width!

We do think we've figured out a solution using hardware but we need to test it. The issue is that we have 15 pins we need to drive in seven states so a timer/dma solution isn't trivial and our PCB isn't currently laid out with the correct pins.

The DWT-CYCNT solution can be used for wide pulse widths but unfortunately we need the pulse widths to be within 1% down to 70ns (we operate at 216MHz).

waclawek.jan
Super User
August 6, 2023

Isn't there an overlooked obvious solution, i.e. a higher-priority interrupt prolonging the 3rd and 7th pulse?

If not, this is probably the price you pay for the performance. The CM7 is an overcomplicated beast, mildly superscalar, with features like speculative jump prefetch (with no public description of its algorithm). I'm not sure whether this grants the 1.7x time penalty, though; that's why the question above.

The asm version (i.e. a function called) has somewhat bigger chance to be consistent, as it sits always at the same address and uses the same registers.

JW

RBack.1
RBack.1Author
Associate III
August 6, 2023

I didn't post the code waiting for our ISR but it basically calls __disable_irq() then enters a while(1).

We've been battling with this overcomplicated best for a while now which is good for our higher level algorithms but really bad at simple things like driving I/O reliably. A hardware solution is difficult as we have 15 pins that need to be driven in seven states but a young engineer thinks he might have developed a solution in his personal time. It'd require us to relay out the board though.

The 1.7x is much larger variation than we've ever seen before that makes me hope maybe there's something else going on that we can fix. The most frustrating part is my code from last year uses an assembly function call and no attention to the overcomplicated device features...yet it V&Vs to show perfect timing! There must be some difference but we just can't figure it out.

Piranha
Principal III
August 7, 2023

KnarfB already noted about L1 cache memories...

Also DWT and SysTick are located in the CPU, therefore accesses to these shouldn't have any delays.

waclawek.jan
Super User
August 8, 2023

> Also DWT and SysTick are located in the CPU, therefore accesses to these shouldn't have any delays.

Reading system areas may have timing consequences, such as they may behave as barriers. I am lazy to look up the details now.

But it reminded me of one more question to @RBack.1 : is the irregular timing observed also when running without debugger attached?

JW

RBack.1
RBack.1Author
Associate III
August 8, 2023

Heh...you are correct that this is a problem I've been battling since taking over this code/PCB almost two years ago. It does show the problems when using the debugger or programming.

However, yesterday a young engineer in my company figured out the problem! He looked at the disassembly between the pulses that were regular and were irregular and the addresses that they occupied. By inserting nops to align them to 8byte boundaries he was able to make them all regular. Using a function instead of a macro (which we switched to from a function in hopes of making our problems better) he ensured that the code would always be aligned on the boundaries correctly, however calling the function with a parameter of 1 resulted in irregularity in the subsequent pulses. Now that we ensure that a minimum value of 2 is used for the function, our pulses are all regular again!

This is the same young engineer that is close to solving how to drive our 15pins in 7 states with timers/dma btw. I can't say enough good things about him. I also have great things to say about several people on these forums as some of you have come through for me several times now. @Tesla DeLorean @TDK are names in addition to yours that I remember having helped me before. I'll tag them in case what the young engineer learned is interesting to them.

And boy-oh-boy is this processor an overcomplicated beast...

waclawek.jan
Super User
August 8, 2023

> align

I thought this is something like that, that's why I wrote above that the function has bigger chance to be consistent as it's always on the same address and always uses the same registers (I mean in one particular binary).

But now I realize that you've mentioned this problem already from a different angle. The picture there got lost during the transition to Khoros; if you still have it, could you please restore it. I gave it a thought back then, but you know, the pressure of family to feed and bills to pay... :)

> drive our 15pins in 7 states with timers/dma

DMA-controlled TIMs? That would probably work, although 15 pins (i.e. at least 4 timers) might be just at the boundary of possible. I definitively wouldn't pursue TIM-triggered-DMA-transfer-to-GPIO at this point, as that is probably prone to too much jitter. For regular waveforms on multiple pins, also LTDC comes into mind, although I'm not sure about the maximum clock.

JW

RBack.1
RBack.1Author
Associate III
August 8, 2023

I looked at couldn't find the picture again. I passed your info about jitter onto the young engineer taking this on as a personal project and he says he did encounter that issue. I'm not sure what approach he's using now as a way to work around that issue.