Precise delay of c51 Some friends suggested that the accuracy of 51 Single-Chip Microcomputer delay program using C language is not enough. In fact, the C language is eventually compiled into an assembly language for running, so the assembly program will be very large, this affects the precision of latency. Here, we will post some online materials for your reference. The following content is reprinted: A Simple Study of 51 MCU Keil C delay Program By: infinitespace studio/isjfk, 1.21.2004 Anyone can repost this article without specifying the original author and source, but it cannot be used for commercial purposes. When a single-chip microcomputer is used, short latency is often required. The latency is short. Generally, the latency is from dozens to hundreds of microseconds (us ). Sometimes it also requires high accuracy. For example, when a single-chip microcomputer is used to drive DS18B20, the error allowed range is less than a dozen us, otherwise it is easy to make mistakes. In this case, using a timer is often a little tricky. In extreme cases, timers have even been used for other purposes. In this case, we need to find another method. When I used to write a single-chip microcomputer program in assembly language, this problem was relatively easy to solve. For example, if we use a 12 MHz crystal oscillator 51 with a delay of 20 us, we can use the following code to meet the general needs: MoV r0, #09 h Loop: djnz r0, loop The instruction period of 51 single-chip microcomputer is 1/12 of the crystal oscillator frequency, that is, 1 US cycle. MoV r0, # 09h requires two extreme periods, while djnz also requires two extreme periods. The number in R0 is (20-2)/2. With this method, you can easily implement latency of less than us. If it takes a longer time, you can use two layers of nesting. And the precision can reach 2 us. In general, this is enough. Now, the more widely used is undoubtedly the C compiler of Keil. C has many advantages, such as easy maintenance, easy to understand, and suitable for large projects. But the disadvantage (I think this is the only drawback of C) is that the real-time performance is not guaranteed and the instruction cycle of code execution cannot be predicted. Therefore, in scenarios with high real-time requirements, compilation and C joint applications are also required. But is there a latency program that needs to be implemented through assembly? To find this answer, I did an experiment. When using C language to implement latency programs, the first thing that comes to mind is the commonly used loop statements in C. The following code is frequently seen on the Internet: Void delay2 (unsigned char I) { For (; I! = 0; I --); } How high is the accuracy of this code? To directly measure the effect of this code, I found the assembly code generated by Keil C based on this code: ; Function _ delay2 (BEGIN) ; Source line #18 ; ---- Variable 'I 'assigned to register 'r7 '---- ; Source line #19 ; Source line #20 0000? C0007: 0000 EF mov A, r7 0001 6003 JZ? C0010 0003 1f dec r7 0004 80fa sjmp? C0007 ; Source line #21 0006? C0010: 0006 22 RET ; Function _ delay2 (end) I really don't know ~~~ I can only see how unexpected this latency program is ~~~ The four major statements require six machine cycles. That is to say, it has a precision of at most 6 us, which is not counted as a lcall and A ret. If we set the root delay length column of the I value assigned when calling a function to a table, it is: I delay time/us 0 6 1 12 2 18 ... Because function calls require two lcall clock periods, the delay time is two more than the execution time of the slave function code. By the way, some friends write such code: Void delay2 (unsigned char I) { Unsigned char; For (A = I;! = 0; --); } Some people may think that this will generate longer assembly code, but it turns out: ; Function _ delay2 (BEGIN) ; Source line #18 ; ---- Variable 'I 'assigned to register 'r7 '---- ; Source line #19 ; Source line #21 ; ---- Variable 'A' assigned to register 'r7 '---- 0000? C0007: 0000 EF mov A, r7 0001 6003 JZ? C0010 0003 1f dec r7 0004 80fa sjmp? C0007 ; Source line #22 0006? C0010: 0006 22 RET ; Function _ delay2 (end) The generated code is the same. However, this is indeed not a good habit. Because there is no need to introduce unnecessary variables here. We will continue to discuss the topic. Some friends even use the code like this for a longer latency: Void delay2 (unsigned long I) { For (; I! = 0; I --); } What is the assembly code generated by this code? In fact, you don't need to know how horrible it is. $ # ^ % & % $ ...... let's take a look: ; Function _ delay2 (BEGIN) ; Source line #18 0000 8f00 R mov I + 03 h, r7 0002 8e00 R mov I + 02 h, R6 0004 8d00 R mov I + 01 H, R5 0006 8c00 R mov I, r4 ; Source line #19 ; Source line #20 0008? C0007: 0008 E4 CLR 0009 FF mov R7, 000a Fe mov R6, 000b FD mov R5, 000c FC mov R4, 000d ab00 R mov R3, I + 03 h 000f aa00 R mov R2, I + 02 h 0011 a900 R mov R1, I + 01 H 0013 a800 R mov r0, I 0015 C3 CLR C 0016 120000 e lcall? C? Ulcmp 0019 601a JZ? C0010 001b e500 R mov A, I + 03 h 001d 24ff Add a, # 0ffh 001f F500 R mov I + 03 h, 0021 e500 R mov A, I + 02 h 0023 34ff addc a, # 0ffh 0025 F500 R mov I + 02 h, 0027 e500 R mov A, I + 01 H 0029 34ff addc a, # 0ffh 002b F500 R mov I + 01 H, 002d e500 R mov A, I 002f 34ff addc a, # 0ffh 0031 F500 R mov I, 0033 80d3 sjmp? C0007 ; Source line #21 0035? C0010: 0035 22 RET ; Function _ delay2 (end) Well, it can indeed be delayed for a long time ~~~ But there is no precision. So, can I use C to achieve accurate latency? I slightly changed the code: Void delay1 (unsigned char I) { While (I --); } Based on experience, the more concise the C code, the more concise the machine code can be obtained. What is the result? Let's take a look at the assembly code it generates. Hopefully, I pressed the "build target" key, and the result was huge: ; Function _ delay1 (BEGIN) ; Source line #13 ; ---- Variable 'I 'assigned to register 'r7 '---- ; Source line #14 0000? C0004: ; Source line #15 0000 ae07 mov R6, ar7 0002 1f dec r7 0003 EE mov A, R6 0004 70fa jnz? C0004 ; Source line #16 0006? C0006: 0006 22 RET ; Function _ delay1 (end) Although the generated code is not the same as the for statement, I can say without a doubt that the efficiency of the two methods is the same. It seems that this is the end, because I really cannot figure out how to simplify the source program. It seems that I will come up with this conclusion: "If we need us-level latency accuracy, we need to use assembly language ." But is that true? I am still unwilling. Because I don't believe that the famous Keil C compiler doesn't even use djnz ??? In fact, the program only needs one loop: djnz R7 and loop. Close to despair (in this case, people can really burst out. Oh, hehahaoha ~~~), I changed it: Void delay1 (unsigned char I) { While (-- I ); } Absent-minded compilation, look at the source code: ; Function _ delay1 (BEGIN) ; Source line #13 ; ---- Variable 'I 'assigned to register 'r7 '---- ; Source line #14 0000? C0004: ; Source line #15 0000 dffe djnz R7 ,? C0004 ; Source line #16 0002? C0006: 0002 22 RET ; Function _ delay1 (end) Days ~~~ A miracle occurs... I think this program can already meet the general needs. If a table is listed: I delay time/us 1 5 2 7 3 9 ... When calculating the latency, the time spent on the two clock cycles of the lcall statement that calls the function is counted. Finally, the result is clear. C can achieve unexpected results as long as it is used properly. Many friends complain that C is much more efficient than Assembly. In fact, if you have a deep understanding of the Compilation Principle of Keil C, you can use the appropriate syntax, optimize the generated C code. Although this seems unlikely, there are still some simple principles to follow: 1. Try to use the unsigned data structure. 2. Try to use the char type. Int is not enough, and then long is used. 3. If possible, do not use floating point. 4. Use concise code, because concise C code based on experience can often generate concise target code (although not in all cases ). 5 .. I can't think of it anymore ~~~
|