3234 views|1 replies

1372

Posts

2

Resources
The OP
 

Use the C language library setjmp/longjmp function for exception recovery [Copy link]

  A long time ago, when I was still using Turbo C 2.0 to write programs, I saw setjmp() and longjmp() functions when browsing library functions in the help function, but I never understood what they were used for. More than 20 years later, I learned about the functions of setjmp() and longjmp() functions from someone else's blog on the Internet, and I think this is a "magic tool" provided by the C language. From the name, you can guess that they implement some kind of "Goto" function, but it must be different from the C language keyword goto. C language has goto, but I never use it when writing programs (the teacher who introduced me to C language told me not to use goto, so I was influenced by this). I only think that when I exit multiple loops... it would be easier to use goto here.

  Although goto can jump directly from a nested loop to the outermost loop, the jump is limited to within a function (because a function in C language is a code module, and the address inside other functions cannot be determined during compilation). Want to jump out of a nested function call? The conventional method is to return from one level of function at a time. Although this keeps the program structure clear, sometimes in order to achieve more efficient implementation, you can use...

  C++ language has try, catch block function, which provides advanced exception handling support, and can realize the above-mentioned "cross-function" exit. The setjmp() and longjmp() functions of C language provide similar exception handling support, but it is implemented at the library function level, not built-in language. The longjmp() function can jump to the location of the setjmp() function call to execute, as if the code returns from the setjmp() function.

  So, what are the benefits of using these two peculiar functions in microcontroller programs?

  In a small thing I just made, I need to perform a timeout check when executing a certain command operation. During this operation, many SPI slave mode transmissions and receptions are used. Because the SPI clock is given externally, I use a loop to repeatedly read the status register while waiting for the SPI status register to update. If you want to add a timeout check, you need to test the state of the timer in the loop, or test a global variable modified by the timer interrupt. In this way, each loop waiting for the SPI status needs to be written more (the response time to the status update will also increase); then, after detecting the timeout failure, a conditional branch is required to transfer to the exception handling (if written with goto, it can be simplified), including the possibility of returning from the nested function. The result is that the code is logically correct but I think it is lengthy and cumbersome.

  So I used setjmp/longjmp to implement this timeout exception handling.

  The first step is to #include <setjmp.h>

  The second step is to define a global variable jmp_buf timeout_jmp;

  The third step is to write the following code in the code of the command operation to be executed:

SysTick_Config (TIMEOUT_VALUE); /* Use SysTick interrupt to capture timeout, the timer must be set */

if ( setjmp (timeout_jmp)) /* A timeout exception occurs, the function will return true*/
{

/* Write the software and hardware reinitialization code required after an exception occurs here */
return ERR_CODE_TIMEOUT; /* The command was not completed due to timeout, end*/
}
/* The following code starts executing the command*/

/* Process omitted */

SysTick- > CTRL = 0; /* Done, timer disabled */

return cmd_status;


  Step 4: Write a helper function to execute longjmp()

void timeout_catch( void )
{
SysTick -> CTRL = 0;
longjmp (timeout_jmp,1);
}

  The last step is to write the timer interrupt handler, which is executed when a timeout occurs.

void SysTick_Handler ( void )
{
asm volatile (
" str %[ret_addr], [sp,#0x18] \n"
::[ret_addr]"r"(timeout_catch) :
);
}

  Implementation principle: The setjmp() function uses a variable of the jmp_buf structure type to save the scene, and longjmp() restores from the saved scene. That is, the parameter of longjmp() provides the address of where the saved scene is stored, and the address to continue execution is also obtained from this storage. The timeout exception capture is triggered by the timer interrupt, so why not call longjmp() in the interrupt handler? Because longjmp() only restores the scene at the software level and knows nothing about the interrupt status, it must return to the non-interrupt mode and then call longjmp(), otherwise the subsequent code will not exit the interrupt. The timeout_catch() I wrote above is used for this purpose - to let the interrupt return to the timeout_catch() function.

  Here I used a special trick again, which is written in ARM Cortex-m0 processor assembly code. Because the address to which the interrupt is to be returned is stored in the stack, the entry address of the timeout_catch() function is written to the original PC register position in the stack - that is, the address of the next instruction to be executed when the interrupt occurs (now there is no need to execute there, because an exception occurs and the execution needs to be canceled). In this way, the interrupt returns to the timeout_catch() function, then turns off the timer first, and then executes longjmp(). At this point, the program is transferred to the exception handling code after the setjmp() call.

  You can disassemble and see what setjmp() and longjmp() do:

080017b8 <setjmp>:
 80017b8:	c0f0      	stmia	r0!, {r4, r5, r6, r7}
 80017ba:	4641      	mov	r1, r8
 80017bc:	464a      	mov	r2, r9
 80017be:	4653      	mov	r3, sl
 80017c0:	465c      	mov	r4, fp
 80017c2:	466d      	mov	r5, sp
 80017c4:	4676      	mov	r6, lr
 80017c6:	c07e      	stmia	r0!, {r1, r2, r3, r4, r5, r6}
 80017c8:	3828      	subs	r0, #40	; 0x28
 80017ca:	c8f0      	ldmia	r0!, {r4, r5, r6, r7}
 80017cc:	2000      	movs	r0, #0
 80017ce:	4770      	bx	lr

080017d0 <longjmp>:
 80017d0:	3010      	adds	r0, #16
 80017d2:	c87c      	ldmia	r0!, {r2, r3, r4, r5, r6}
 80017d4:	4690      	mov	r8, r2
 80017d6:	4699      	mov	r9, r3
 80017d8:	46a2      	mov	sl, r4
 80017da:	46ab      	mov	fp, r5
 80017dc:	46b5      	mov	sp, r6
 80017de:	c808      	ldmia	r0!, {r3}
 80017e0:	3828      	subs	r0, #40	; 0x28
 80017e2:	c8f0      	ldmia	r0!, {r4, r5, r6, r7}
 80017e4:	1c08      	adds	r0, r1, #0
 80017e6:	d100      	bne.n	80017ea <longjmp+0x1a>
 80017e8:	2001      	movs	r0, #1
 80017ea:	4718      	bx	r3

  They save and restore several registers, including the PC and SP registers, so that the program stack can be restored to the state when setjmp() is called - the stack grows downward in subsequent sub-function calls, and when the SP register is restored, the local variables of those sub-functions are invalid. The on-site recovery done by longjmp() is limited to this. Changes to hardware registers, dynamic memory allocation, etc., must be handled by the software programmer.

  In my implementation above, the SysTick timer is used to trigger the timeout action. If the normal operation ends before the SysTick timer reaches 0, it will be turned off and no interrupt will be triggered. In this way, the code that handles things does not need to detect whether a timeout has occurred. When a timeout occurs, no matter where the code is executed, the SysTick interrupt service routine will be transferred to the timeout_catch() function, and longjmp() will be called to restore the saved scene. In this way, the return value of setjmp() can be used to determine that a timeout has occurred in the initial code, and then subsequent processing can be performed.

This post is from MCU

Latest reply

Learned   Details Published on 2021-10-8 20:25

赞赏

1

查看全部赞赏

 

994

Posts

3

Resources
2
 

Learned

This post is from MCU
 
 

Guess Your Favourite
Just looking around
Find a datasheet?

EEWorld Datasheet Technical Support

EEWorld
subscription
account

EEWorld
service
account

Automotive
development
circle

Copyright © 2005-2024 EEWORLD.com.cn, Inc. All rights reserved 京B2-20211791 京ICP备10001474号-1 电信业务审批[2006]字第258号函 京公网安备 11010802033920号
快速回复 返回顶部 Return list