Teacher Wei Dongshan's RTOS introductory course (1) RTOS introduction, familiar with bare metal assembly logic

RTOS Introductory Course by Mr. Wei Dongshan

Course Link: Wei Dongshan Live Open Class: RTOS Practical Project Realization of Multitasking System Section 1: Bare Metal Program Framework and Defects_哔哩哔哩_bilibili

Introduction to RTOS

Bare Metal: Execution in fixed order.

Interruption: You can concentrate on doing things in the loop until an interruption is triggered. You can also set a flag in the interrupt to detect the execution in the loop to prevent the interrupt from timing out.

Timer: When there are too many tasks, it is not suitable to use interrupts. You can use a timer to set the execution frequency of different tasks, for example, A executes once in 1ms, B executes once in 2ms... But they will affect each other, for example, if A is stuck, it will affect B.

Another solution is a state machine. Each function sets several states, and each time a part of the state is executed, the current state is retained to exit, and the next time it enters, it continues to execute.

image-20230818232306800

State machine has four concepts: state, event, action, transformation.

States are states.

An event is a trigger condition for performing an operation. For example, when the door is open, I initiate a door closing event, and when the door is closed, I initiate a door opening event.

Actions are specific behaviors triggered by events.

A transition is a switch between states.

The state machine is cumbersome and not so optimal.

For example, RTOS can set each program to be executed according to a certain time slice, and switch by itself when the time comes, without having to write a state machine so complicated. And now the RTOS ecology is better (especially rt-thread), and most developments require RTOS. In fact, it is simpler to use an RTOS.

ARM-based

What is the procedure?

When running the program, burn the program into the flash file first, put the data into RAM (variable), and read the instruction by CPU to fetch the data and write the data. The data in RAM is obtained in the registers of the CPU.

Here we focus on the 6 arm instructions.

  1. read command. LDR R0,[R1,#4], specifying rd, rs, length. LDR is fixed to take 4B, and take it from R1+4 address.
  2. write command. STR R0,[R1,#4].
  3. addition and subtraction.ADD R0,R1,R2 ADD R0,R0,#1 SUB R0,R1,R2
  4. Compare. CMP R0,R1The results are stored in the PSR.
  5. Jump B BL, BL saves the return address after the jump.

Analyze the assembly code of C and understand the program

image-20230819133726104

Take a very simple program as an example. After Keil enters the debug mode, you can see the assembly code of the corresponding code.

First, automatically push the stack r3 lr through the PUSH instruction and modify the sp pointer to save the r3 register and function return address

The second sentence sets r0 = the address of a.

The third sentence takes out the value of a according to the address and stores it in r0.

In the fourth sentence, the value of r0 is stored in the position of stack 0, because after the stack was pushed just now, lr r3 is stored in the stack from high to low, that is to say, r0 actually stores the data in the position of r3 in the stack, and r3 enters The stack occupies a bit in the stack.

Then the stack is popped, lr is assigned to pc for the function to return, and r3 gets the value written in the stack.

Look at the second program:

int add_val(int *pa, int *pb)
{
    
    
	volatile int tmp;
	tmp = *pa;
	tmp += *pb;
	return tmp;
}


int mymain()
{
    
    
	volatile int a = 1;
	volatile int b = 2;
	volatile int c;
	
	c = add_val(&a, &b);
	
	return 0;
}

The compiled code (in the .dis file):

    i.mymain
    mymain
        0x08000372:    b50e        ..      PUSH     {r1-r3,lr}
        0x08000374:    2001        .       MOVS     r0,#1
        0x08000376:    9002        ..      STR      r0,[sp,#8]
        0x08000378:    2002        .       MOVS     r0,#2
        0x0800037a:    9001        ..      STR      r0,[sp,#4]
        0x0800037c:    a901        ..      ADD      r1,sp,#4
        0x0800037e:    a802        ..      ADD      r0,sp,#8 ;传参
        0x08000380:    f7ffffca    ....    BL       add_val ; 0x8000318
        0x08000384:    9000        ..      STR      r0,[sp,#0]
        0x08000386:    2000        .       MOVS     r0,#0
        0x08000388:    bd0e        ..      POP      {r1-r3,pc}
        0x0800038a:    0000        ..      MOVS     r0,r0
        
    i.add_val
    add_val
        0x08000318:    b508        ..      PUSH     {r3,lr}
        0x0800031a:    4602        .F      MOV      r2,r0
        0x0800031c:    6810        .h      LDR      r0,[r2,#0]
        0x0800031e:    9000        ..      STR      r0,[sp,#0]
        0x08000320:    6808        .h      LDR      r0,[r1,#0]
        0x08000322:    9b00        ..      LDR      r3,[sp,#0]
        0x08000324:    4418        .D      ADD      r0,r0,r3
        0x08000326:    9000        ..      STR      r0,[sp,#0]
        0x08000328:    9800        ..      LDR      r0,[sp,#0]
        0x0800032a:    bd08        ..      POP      {r3,pc}

It can be seen that several function parameters r0 r1... are passed in, and those exceeding r3 are generally pushed onto the stack.

Parameter problem, let's try the second code: add with 4 parameters.

program:

int add_val(int a, int b, int c, int d)
{
    
    
	return a+b+c+d;
}


int mymain()
{
    
    
	volatile int a = 1;
	volatile int b = 2;
	volatile int c = 3;
	volatile int d = 4;
	volatile int sum;
	
	sum = add_val(a,b,c,d);
	
	return 0;
}
i.mymain
    mymain
        0x0800036a:    b500        ..      PUSH     {lr}
        0x0800036c:    b085        ..      SUB      sp,sp,#0x14
        0x0800036e:    2001        .       MOVS     r0,#1
        0x08000370:    9004        ..      STR      r0,[sp,#0x10]
        0x08000372:    2002        .       MOVS     r0,#2
        0x08000374:    9003        ..      STR      r0,[sp,#0xc]
        0x08000376:    2003        .       MOVS     r0,#3
        0x08000378:    9002        ..      STR      r0,[sp,#8]
        0x0800037a:    2004        .       MOVS     r0,#4
        0x0800037c:    9001        ..      STR      r0,[sp,#4]
        0x0800037e:    e9dd3201    ...2    LDRD     r3,r2,[sp,#4]
        0x08000382:    e9dd1003    ....    LDRD     r1,r0,[sp,#0xc]
        0x08000386:    f7ffffc7    ....    BL       add_val ; 0x8000318
        0x0800038a:    9000        ..      STR      r0,[sp,#0]
        0x0800038c:    2000        .       MOVS     r0,#0
        0x0800038e:    b005        ..      ADD      sp,sp,#0x14
        0x08000390:    bd00        ..      POP      {pc}
        0x08000392:    0000        ..      MOVS     r0,r0

After storing lr r3 r2 r1 r0, load r3 r2 r1 r0 from low to high address (probably because the order of input stacking and function parameters is reversed), and then jump.

i.add_val
    add_val
        0x08000318:    b510        ..      PUSH     {r4,lr}
        0x0800031a:    4604        .F      MOV      r4,r0
        0x0800031c:    1860        `.      ADDS     r0,r4,r1
        0x0800031e:    4410        .D      ADD      r0,r0,r2
        0x08000320:    4418        .D      ADD      r0,r0,r3
        0x08000322:    bd10        ..      POP      {r4,pc}

This involves register protection in functions. Recently, I was looking at the architecture of MIPS, which is divided into different registers (t, s, a...) arm also has different functions.

r0-r3 pass parameters. r13 sp, r14 lr, r15 pc.

The three functions that pass parameters can be used at will without protection, and it doesn’t matter if the values ​​are different when they return. r4-r11 can also be used, but must be saved and restored. The add function in the above example uses r4.

For example, if the code were changed to:

int add_val(int a, int b, int c, int d)
{
    
    
	// 故意使用R4
	register int sum asm("r4");
	
	sum = a+b+c+d;
	
	return sum;
}

compilation:

    i.add_val
    add_val
        0x08000318:    b530        0.      PUSH     {r4,r5,lr}
        0x0800031a:    4604        .F      MOV      r4,r0
        0x0800031c:    1865        e.      ADDS     r5,r4,r1
        0x0800031e:    4415        .D      ADD      r5,r5,r2
        0x08000320:    18e8        ..      ADDS     r0,r5,r3
        0x08000322:    bd30        0.      POP      {r4,r5,pc}

r5 is equivalent to the intermediate calculation result, and both he and r4 should reply.

interrupt handling

Save the scene-handle the interruption-restore the scene, and continue the execution of the source program. Which registers to save?

  • First of all, the parameter register must be saved, otherwise the function has not processed the parameters yet. An interrupt parameter is lost.

  • r4-r11 must be saved, otherwise the functions have not been pushed to the stack to save them. If they are lost, they cannot be retrieved, and they cannot be restored.

  • lr needs to be saved, the same reason, if lr is modified when it is not pushed to the stack, it cannot jump back to the original position.

In fact, all registers must be saved at the moment an interrupt occurs!

The c interrupt processing function we call can only ensure that r4-r11 is not destroyed, so if you want to ensure that all registers can be saved, you must save them before calling the c function. Hardware saves other registers automatically.

When restoring, the hardware automatically restores other registers, and the c function guarantees to restore r4-r11.

The registers to be saved by the hardware are r0-r3, r12, lr, and the current interrupt return position. A typical misunderstanding is, isn't lr the current interrupt return location? Not really. For example, the main function calls the A function, and an interrupt occurs halfway through the execution of the A function. At this time, the value in lr is the address of the location required for the A function to return to the main function, and the interrupt returns to the A function. The address needs to be saved separately.

Guess you like

Origin blog.csdn.net/jtwqwq/article/details/132383331