2
\$\begingroup\$

I need to have programmable pause with precision as high as possible. To accomplish this I have the following GCC code:

void delay(unsigned char d){
  volatile unsigned char i=d;
  while(i>0) i--;
}

Which is compiled to:

1cc:    89 81           ldd r24, Y+1    ; 0x01
1ce:    81 50           subi    r24, 0x01   ; 1
1d0:    89 83           std Y+1, r24    ; 0x01
1d2:    89 81           ldd r24, Y+1    ; 0x01
1d4:    81 11           cpse    r24, r1
1d6:    fa cf           rjmp    .-12        ; 0x1cc <__vector_1+0x2c>

(I'm showing just the loop core code). This leads to the fact that the precision is 7 cycles which is not very acceptable. However I see that the compiler made his job not as fast as possible: If the i variable would be r24 register I will save 3 operations and the code would be almost twice as fast.

So how can I tell to the compiler that I want this variable to be in a register?

PS. I'd consider to pause with programmable number on nop's. But I can not imagine how this can be achieved. AVR has no instructions to branch to calculated address. As far as I know stack is not directly accessible in AVR (if it would I could push needed value to the stack and execute ret instruction to jump to the needed program address - it is also a tricky job but it would be at least considerable).

UPDATE After I changed volatile to register keyword (as was described in one of the answers) I've got the following code:

 14e:   81 50           subi    r24, 0x01   ; 1
 150:   f1 f7           brne    .-4         ; 0x14e <__vector_6+0x1c>

So I reduced the cycle from 7 to 2 cycles. Which is far better than I could expect.

\$\endgroup\$
  • 1
    \$\begingroup\$ Why did you make it volatile? That pretty much forces the compiler to use a memory location, and access it on every loop iteration. Try register instead. \$\endgroup\$ – Dave Tweed Oct 24 '14 at 11:18
  • \$\begingroup\$ @DaveTweed that helped! The cycle become only 2 instructions long. I used volatile to prevent compiler from "optimizing out" the code seemed to do nothing. Would you write an answer which I could vote and accept? \$\endgroup\$ – Roman Matveev Oct 24 '14 at 11:22
  • 1
    \$\begingroup\$ Volatile is not usually a good keyword to use to stop a compiler optimising code like that. Many compilers have #pragma directives to disable optimisation for small sections of code. \$\endgroup\$ – David Oct 24 '14 at 11:26
5
\$\begingroup\$

Don't declare your variable as volatile. That pretty much forces the compiler to use a memory location, and to access it on every loop iteration. Use register instead.

\$\endgroup\$
  • 2
    \$\begingroup\$ Wont't work as delay, because the compiler will optimize the loop away altogether. \$\endgroup\$ – Turbo J Oct 24 '14 at 12:43
  • 2
    \$\begingroup\$ @TurboJ: The OP says you're wrong. Besides, you can control the level of optimization used, and in some compilers, you can vary it for individual functions. But the better solution overall is to use hardware timers for precision timing. \$\endgroup\$ – Dave Tweed Oct 24 '14 at 13:25
5
\$\begingroup\$

The conventional way to achieve what you want is to hand code the delay subroutine in the native assembly language. Almost all development tool sets that are C compilers will have the capability to include assembly language modules into the build.

A common trick used by experienced programmers is to first build the delay routine in C code as you have done. Then by looking at the compiled machine code you get a template of how to write the same routine in assembler code and then permitting you to optimize the critical sections. The advantage of this approach is that the compiler nicely "shows you how" to setup the entry and exit to/from the subroutine to be compatible with being called from the main C code.

\$\endgroup\$
  • \$\begingroup\$ I'd be aware of the fact that my assembler code will became wrong if the compiler will do its job in another way for some reason like: adding another local variable, changing optimization methods or something else. \$\endgroup\$ – Roman Matveev Oct 24 '14 at 11:27
  • 1
    \$\begingroup\$ @RomanMatveev - Indeed this is true. Change a program expect changes in behavior. But the same consideration comes to play leaving the whole delay code in the C code too. Changing the code design could change how the compiler sees the register usage in the subroutine. Also change compiler versions, optimization levels, or even port to a different compiler and the expected behavior of the C code delay routine is highly likely to change from what you saw today. Commit the routine to assembler and then code won't change unless you decide to change it. \$\endgroup\$ – Michael Karas Oct 24 '14 at 11:35
  • \$\begingroup\$ Keep in mind that particular tool sets define standard processes for passing parameters to subroutines and for sending back return values. The methods to do this are unlikely to change with compiler versions or optimization levels because if it did change the customer base will get really ticked off when their assembly language routines fail or pre-compiled libraries fail to function properly when linked to a current compile. \$\endgroup\$ – Michael Karas Jul 29 '16 at 5:21

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.