Switch context to task and go back, atmega328p

Go To Last Post
34 posts / 0 new
Author
Message
#1
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

I'm learning how to do context switching and i have selected the ATMEGA328p to do that, because is more accessible and have a great community.

 

I learned theoretically what is context switching but not how to do that practically. Theoretically it's necessary 1. save context (system), 2. switch context to task (user), 3. return context (system)

 

My try resulted in this code, but that don't work, why?

 

OBS: I'm using the TIMER1 in CTC mode with frequency of 1HZ to debug better.

 

int createdContext = 0; 

int stackTASKONE = RAMEND - 124;

void TASKONE()
{
    // turn on LED in DIGITAL PIN 8
    DDRB  |= _BV(PB0);
    PORTB |= _BV(PB0);
}

// Task switching, ISR_NAKED is necessary to prevent compiler to manipulate the stack
ISR(TIMER1_COMPA_vect, ISR_NAKED)
{
    uint8_t pcl, pch, spl, sph;

    // Save context
    asm volatile ("push   r0             \n\t"    // r0
                  "in     r0, __SREG__   \n\t"
                  "push   r0             \n\t"    // SREG
                  "push   r1             \n\t"    // r1
                  "clr    r1             \n\t"    // GCC expects r1 to be null
                  "push   r2             \n\t"    // r2
                  "push   r3             \n\t"    // r3
                  "push   r4             \n\t"    // r4
                  "push   r5             \n\t"    // r5
                  "push   r6             \n\t"    // r6
                  "push   r7             \n\t"    // r7
                  "push   r8             \n\t"    // r8
                  "push   r9             \n\t"    // r9
                  "push   r10            \n\t"    // r10
                  "push   r11            \n\t"    // r11
                  "push   r12            \n\t"    // r12
                  "push   r13            \n\t"    // r13
                  "push   r14            \n\t"    // r14
                  "push   r15            \n\t"    // r15
                  "push   r16            \n\t"    // r16
                  "push   r17            \n\t"    // r17
                  "push   r18            \n\t"    // r18
                  "push   r19            \n\t"    // r19
                  "push   r20            \n\t"    // r20
                  "push   r21            \n\t"    // r21
                  "push   r22            \n\t"    // r22
                  "push   r23            \n\t"    // r23
                  "push   r24            \n\t"    // r24
                  "push   r25            \n\t"    // r25
                  "push   r26            \n\t"    // r26
                  "push   r27            \n\t"    // r27
                  "push   r28            \n\t"    // r28
                  "push   r29            \n\t"    // r29
                  "push   r30            \n\t"    // r30
                  "push   r31            \n\t"    // r31
                  "in     %0, __SP_L__   \n\t"    // Save current Stack Pointer
                  "in     %1, __SP_H__   \n\t"
                  :"=r"(spl), "=r"(sph));

    // Prepare next task Stack Pointer
    spl = (uint8_t) stackTASKONE;
    sph = (uint8_t) stackTASKONE >> 8;

    if(createdContext == 0) {
        createdContext = 1;

        // We need to put the Program Counter on a new stack and populate it
        pcl = (uint8_t) ((uint16_t)TASKONE);
        pch = (uint8_t) ((uint16_t)TASKONE >> 8);

        // Create a whole context for the new task
        asm volatile ("out    __SP_L__, %0   \n\t"
                      "out    __SP_H__, %1   \n\t"
                      "push   %2             \n\t"    // push PC_L first
                      "push   %3             \n\t"    // push PC_H then
                      "clr    r0             \n\t"
                      "push   r0             \n\t"    // r0
                      "in     r0, __SREG__   \n\t"
                      "push   r0             \n\t"    // SREG
                      "clr    r0             \n\t"
                      "clr    r1             \n\t"    // GCC expects r1 to be null
                      "push   r0             \n\t"    // r1
                      "push   r0             \n\t"    // r2
                      "push   r0             \n\t"    // r3
                      "push   r0             \n\t"    // r4
                      "push   r0             \n\t"    // r5
                      "push   r0             \n\t"    // r6
                      "push   r0             \n\t"    // r7
                      "push   r0             \n\t"    // r8
                      "push   r0             \n\t"    // r9
                      "push   r0             \n\t"    // r10
                      "push   r0             \n\t"    // r11
                      "push   r0             \n\t"    // r12
                      "push   r0             \n\t"    // r13
                      "push   r0             \n\t"    // r14
                      "push   r0             \n\t"    // r15
                      "push   r0             \n\t"    // r16
                      "push   r0             \n\t"    // r17
                      "push   r0             \n\t"    // r18
                      "push   r0             \n\t"    // r19
                      "push   r0             \n\t"    // r20
                      "push   r0             \n\t"    // r21
                      "push   r0             \n\t"    // r22
                      "push   r0             \n\t"    // r23
                      "push   r0             \n\t"    // r24
                      "push   r0             \n\t"    // r25
                      "push   r0             \n\t"    // r26
                      "push   r0             \n\t"    // r27
                      "push   r0             \n\t"    // r28
                      "push   r0             \n\t"    // r29
                      "push   r0             \n\t"    // r30
                      "push   r0             \n\t"    // r31
                      ::"r"(spl), "r"(sph), "r"(pcl), "r"(pch)
                      );
    } else {
        // Restore saved SP
        asm volatile (
                      "out    __SP_L__, %0   \n\t"
                      "out    __SP_H__, %1   \n\t"
                      ::"r"(spl), "r"(sph)
                      );
    }

    // Restore context
    asm volatile ("pop    r31            \n\t"    // r31
                  "pop    r30            \n\t"    // r30
                  "pop    r29            \n\t"    // r29
                  "pop    r28            \n\t"    // r28
                  "pop    r27            \n\t"    // r27
                  "pop    r26            \n\t"    // r26
                  "pop    r25            \n\t"    // r25
                  "pop    r24            \n\t"    // r24
                  "pop    r23            \n\t"    // r23
                  "pop    r22            \n\t"    // r22
                  "pop    r21            \n\t"    // r21
                  "pop    r20            \n\t"    // r20
                  "pop    r19            \n\t"    // r19
                  "pop    r18            \n\t"    // r18
                  "pop    r17            \n\t"    // r17
                  "pop    r16            \n\t"    // r16
                  "pop    r15            \n\t"    // r15
                  "pop    r14            \n\t"    // r14
                  "pop    r13            \n\t"    // r13
                  "pop    r12            \n\t"    // r12
                  "pop    r11            \n\t"    // r11
                  "pop    r10            \n\t"    // r10
                  "pop    r9             \n\t"    // r9
                  "pop    r8             \n\t"    // r8
                  "pop    r7             \n\t"    // r7
                  "pop    r6             \n\t"    // r6
                  "pop    r5             \n\t"    // r5
                  "pop    r4             \n\t"    // r4
                  "pop    r3             \n\t"    // r3
                  "pop    r2             \n\t"    // r2
                  "pop    r1             \n\t"    // r1
                  "pop    r0             \n\t"    // SREG
                  "out    __SREG__, r0   \n\t"
                  "pop    r0             \n\t"    // r0
                  );
}

 

 

Last Edited: Sun. Jun 2, 2019 - 01:58 AM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

You need to prepare your task stacks first. Then the task switch is only concerned with switching the tasks. Have a look at the freeRtos code. As to why your code doesnt work - run it in the studio simulator. You should see why pretty quickly.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Try:

 

volatile int createdContext = 0;

 

--Mike

 

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Thanks, i will install this program and do what you said.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Unfortunately don't work 

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

As Kartman says take a look at the FreeRTOS site. They have an explanation of their implementation and the specific CPU the chose to document is AVR so it'll show exactly how to do this (and you'll see a LOT of similarity with what you have already! ;-)

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Hey! Thanks, i took a look and don't found the real code implemented by FreeRTOS but found this explanation: https://xivilization.net/~marek/binaries/multitasking.pdf

 

Reading this i don't find my error, my routines to save and restore context are almost the same.

 

I'm trying using the Atmel Studio for debug, but not found the error yet.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Look at how you are restoring the stack pointer and think about that.

Last Edited: Sun. Jun 2, 2019 - 03:17 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0
                  "in     %0, __SP_L__   \n\t"    // Save current Stack Pointer
                  "in     %1, __SP_H__   \n\t"
                  :"=r"(spl), "=r"(sph));

    // Prepare next task Stack Pointer
    spl = (uint8_t) stackTASKONE;
    sph = (uint8_t) stackTASKONE >> 8;

 

Didn't you forget to save spl and sph before overwriting them?

Besides, these variables are local to the ISR, how will they keep SP?

And I think naked functions don't have exit code, either. So you need to append an "reti" instruction to the end.

Last Edited: Sun. Jun 2, 2019 - 04:59 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

d

 

Doing magic with a USD 7 Logic Analyser: https://www.avrfreaks.net/comment/2421756#comment-2421756

Bunch of old projects with AVR's: http://www.hoevendesign.com

Last Edited: Mon. Jun 24, 2019 - 08:52 AM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

I agree that FreeRTOS is too complicated, both for 8-bit architectures and for learning the basics of task-switching.

Hint: For each task there is typically a task-control-block.  Go look at what is in there.

 

Hoping this does not hijack the thread, but ...

While "feature-rich" RTOS it not a good fit for the AVR architecture, task-switching is, IMO.   I have generated priority-based task switching which right now requires ~400 bytes program, and ~50 bytes ram and locks out interrupts for about 20 cycles: I have not started optimization.  It also provides #define for the user to decide which registers are saved.  The idea is that the AVR architecture provides a lot of interrupt driven I/O.   A lightweight tasking system could be used to offload I/O processing in interrupt context in order to get interrupt latency down.

Last Edited: Sun. Jun 2, 2019 - 05:33 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

I don't think anyone was suggesting actually using FreeRTOS (though it has an AVR port) simply that it has notes about how to go about implementing preemptive context switching and those notes specifically target the AVR so are a VERY interesting read.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

"Dare to be naïve." - Buckminster Fuller

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

dd

Doing magic with a USD 7 Logic Analyser: https://www.avrfreaks.net/comment/2421756#comment-2421756

Bunch of old projects with AVR's: http://www.hoevendesign.com

Last Edited: Mon. Jun 24, 2019 - 08:52 AM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

I don't know if it is helpful, but there is a simple multitasking for the mega328 that I have used for education for a long time.
It is fixed to 4 tasks in simple time division every 1000 clocks.
It was very similar to your code, so I modified my native language comment for posting.

 

 

Attachment(s): 

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Paulvdh wrote:
but they are dissimilar enough from the Mega's that you'll have a substantial learning curve if you want to get to know them.
That used to be the case until AVR-0/AVR-1. Now all recent Microchip release for Tiny" / "Mega" actually "look" like Xmega internally so as the "good chips" in the Microchip range (loaded/cheap) move to being AVR-0/AVR-1 it's probably time to grasp the Xmega nettle!

 

Xmega/"Xtiny" actually probably make a pretty good stepping stone towards Cortex. Their peripherals are undoubtedly more complex than traditional tiny/mega so it is a step up, but it's perhaps not quite as complex as AVR32/Cortex peripherals so it's probably a good learning experience for anyone headed in that direction.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

kabasan wrote:
... but there is a simple multitasking for the mega328 that I have used for education for a long time.
Likewise for mega324P from ones at the University of California Riverside and Irvine :

RIOS -- Simple free task scheduler in less than 50 lines of C

(bottom)

Porting to AVR microcontrollers

  • For more information on running RIOS on an AVR, check here.

...

 

"Dare to be naïve." - Buckminster Fuller

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

d

Doing magic with a USD 7 Logic Analyser: https://www.avrfreaks.net/comment/2421756#comment-2421756

Bunch of old projects with AVR's: http://www.hoevendesign.com

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

MattRW - You speak like my teacher, but thanks i get it.

 

El Tangas - You're really right. I didn't know about the exit code in naked functions. Thats is another big problem, i will fix it.

 

Paulvdh - I think the same, it's getting complicated. For RTOS in real applications AVR it's not the first choise.. but to study it's much easier to understand, manipulate and simulate, thanks Atmel Studio... 

 

and, yeah, it's not easy manipulate to invent the wheel again, but it's for learning, i think i like suffer..... this activity is actually for one project multidisciplinary of my course that envolves microprocessors and real time applications.

 

kabasan - Thank u! I will take a look..

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Hey guys, i have updates in my code.

 

Actually this works for the first interruption, after that the system restart  and do it again and restart eternally.... I don't know why. Can see any error?

 

volatile uint8_t spl = 0;
volatile uint8_t sph = 0;

uint16_t spTASK = 0x089B;

void TASKONE()
{
    // turn on LED in DIGITAL PIN 8
    DDRB  |= _BV(PB0);
    PORTB |= _BV(PB0);
}

// Task switching, ISR_NAKED is necessary to prevent compiler to manipulate the stack
ISR(TIMER1_COMPA_vect, ISR_NAKED)
{
    uint8_t splTASK, sphTASK;
    
    // Save context
    asm volatile("CLI               \n\t");
    asm volatile("PUSH R0           \n\t");
    asm volatile("IN   R0, __SREG__ \n\t");
    asm volatile("PUSH R0           \n\t");
    asm volatile("PUSH R1           \n\t");
    asm volatile("CLR  R1           \n\t");
    asm volatile("PUSH R2           \n\t");
    asm volatile("PUSH R3           \n\t");
    asm volatile("PUSH R4           \n\t");
    asm volatile("PUSH R5           \n\t");
    asm volatile("PUSH R6           \n\t");
    asm volatile("PUSH R7           \n\t");
    asm volatile("PUSH R8           \n\t");
    asm volatile("PUSH R9           \n\t");
    asm volatile("PUSH R10          \n\t");
    asm volatile("PUSH R11          \n\t");
    asm volatile("PUSH R12          \n\t");
    asm volatile("PUSH R13          \n\t");
    asm volatile("PUSH R14          \n\t");
    asm volatile("PUSH R15          \n\t");
    asm volatile("PUSH R16          \n\t");
    asm volatile("PUSH R17          \n\t");
    asm volatile("PUSH R18          \n\t");
    asm volatile("PUSH R19          \n\t");
    asm volatile("PUSH R20          \n\t");
    asm volatile("PUSH R21          \n\t");
    asm volatile("PUSH R22          \n\t");
    asm volatile("PUSH R23          \n\t");
    asm volatile("PUSH R24          \n\t");
    asm volatile("PUSH R25          \n\t");
    asm volatile("PUSH R26          \n\t");
    asm volatile("PUSH R27          \n\t");
    asm volatile("PUSH R28          \n\t");
    asm volatile("PUSH R29          \n\t");
    asm volatile("PUSH R30          \n\t");
    asm volatile("PUSH R31          \n\t");
    asm volatile("IN R30, __SP_L__  \n\t");
    asm volatile("IN R31, __SP_H__  \n\t");
    asm volatile("STS  %0, R30      \n\t"
                 "STS  %1, R31      \n\t"
                 :"=m"(spl), "=m"(sph)
                 );

    // Prepare next task Stack Pointer
    uint8_t sphTASK = (spTASK >> 8);
    uint8_t splTASK = (uint8_t) spTASK;

    // Restore saved SP
    asm volatile (
                  "out    __SP_L__, %0   \n\t"
                  "out    __SP_H__, %1   \n\t"
                  ::"r"(splTASK), "r"(sphTASK)
                  );

    TASKONE();
    
     // Restore context
    asm volatile(
                 "LDS  R30, %0     \n\t"
                 "LDS  R31, %1     \n\t"
                 : "=m"(spl), "=m"(sph));
    
    asm volatile("OUT __SP_L__, R30\n\t");
    asm volatile("OUT __SP_H__, R31\n\t");
    asm volatile("POP R31          \n\t");
    asm volatile("POP R30          \n\t");
    asm volatile("POP R29          \n\t");
    asm volatile("POP R28          \n\t");
    asm volatile("POP R27          \n\t");
    asm volatile("POP R26          \n\t");
    asm volatile("POP R25          \n\t");
    asm volatile("POP R24          \n\t");
    asm volatile("POP R23          \n\t");
    asm volatile("POP R22          \n\t");
    asm volatile("POP R21          \n\t");
    asm volatile("POP R20          \n\t");
    asm volatile("POP R19          \n\t");
    asm volatile("POP R18          \n\t");
    asm volatile("POP R17          \n\t");
    asm volatile("POP R16          \n\t");
    asm volatile("POP R15          \n\t");
    asm volatile("POP R14          \n\t");
    asm volatile("POP R13          \n\t");
    asm volatile("POP R12          \n\t");
    asm volatile("POP R11          \n\t");
    asm volatile("POP R10          \n\t");
    asm volatile("POP R9           \n\t");
    asm volatile("POP R8           \n\t");
    asm volatile("POP R7           \n\t");
    asm volatile("POP R6           \n\t");
    asm volatile("POP R5           \n\t");
    asm volatile("POP R4           \n\t");
    asm volatile("POP R3           \n\t");
    asm volatile("POP R2           \n\t");
    asm volatile("POP R1           \n\t");
    asm volatile("POP R0           \n\t");
    asm volatile("OUT __SREG__, R0 \n\t");
    asm volatile("POP R0           \n\t");
    asm volatile("SEI              \n\t");
    asm volatile("RETI             \n\t"); 
}

 

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

As I suggested in #2 - run your code in the Atmel Studio simulator. Single step through the code and see where it goes off the rails. 

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

I did that.. but I will still trying.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Make sure are restoring the stack pointer in both direction.  Usually that means saving SP for both tasks.  Unless you are using the spl,spl for both.

Last Edited: Mon. Jun 10, 2019 - 01:38 AM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Omg, you're totally right. I missed it totally...

I'm trying to do store the SPL and SPH but without success... Inline assembly hates me.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

freakshow wrote:
Inline assembly hates me.
Then why do it in inline asm in the first place? Surely the entire ISR can be in a .S file with cosmetic assembler source?

 

BTW I'm not sure I follow the logic of your example anyway. If you are going to task switch don't you need at least TWO tasks to switch between. So far I see only one (TASKONE) and a very suspicious fixed value of:

uint16_t spTASK = 0x089B;

You may want to google "TCB". (that is "task control block") - it's the basis of a pre-empting OS. Basically a snapshot of the entire "environment" in which one of N tasks may run.

Last Edited: Mon. Jun 10, 2019 - 08:39 AM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

There are other problems here but the main (biggest) is that you are calling TASKONE() before you have fully restored its CPU register context. I.e. poped off registers R0-R31 and SREG.

 

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Again I refer to my comment in #2. I find it surprising that you don't find the problem when using the simulator - you should be able to easily see where the stack pointer is pointing and the value of SREG.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

If you don't read the entire FreeRTOS implementation detail at least read from this page on (following the "Next:" link on each subsequent page):

 

https://www.freertos.org/implementation/a00019.html

 

That shows what goes where as the context switch is being made - what needs to be stored and then replaced.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

@clawson - I really don't know if is possible to do it with other way. To understand my example you need to imagine this working in a real application where tasks can be created in time of execution and the system don't know the stack pointer previously. I'm using in that way (inline assembly) because my tasks stackpointers are allocated in a vector and I don't know the value previously the compiler time. I'm not sure if I understand what you said about task switching.. I can't lose the information of the previously task.. she may not have finished yet.

I will look TCB to verify what I missed out and your link.

N.Winterbottom - You're right. Except for this, what more?

Anyway.. thanks all you guys. I will search a little bit more and still looking what I'm missing in the simulator.. I come back to tell you what is when I found.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

The sequence is:

 

1. save current task

2. change stack to next task

3. restore next task

4. return

 

To do #3 - you need to have saved a context frame already on the next task's stack and have the address of the initial function you wish to run on the stack.

 

 

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Hey guys, i have new code that  works in the simulator. Don't realised no mistake comparing the execution flow with the information that you brought here (in simulator for two tasks). 

 

Executing in real board (atmega328p) it's works too, for two tasks (one task to turn on led and another do turn off).

 

But noticed two things happen in real board:

 

1. After a while system restarts unexpectedly

2. When put 3 tasks the code don't switch context fine to the thirty task, the context swith of TASK0 -> TASK1, TASK1 -> TASK2 and stop when comes to TASK2 -> TASK3. I'm using the Round Robin algorithm for scheduler.

 

I feel like the program is reading trash in stack.

 

Last Edited: Wed. Jun 12, 2019 - 03:11 AM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

freakshow wrote:
1. After a while system restarts unexpectedly
That often says "memory leak". Over time it keeps using more and more RAM until there isn't any more - what do you see in the simulator? In particular keep an eye on SP.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 1

There are other problems here but the main (biggest) is that you have an IF THEN ELSE program flow problem. At line 121 you always end up returning control to the task you interrupted in the first place.

 

It's probably time now to step away from the computer.

  • Find a large sheet of paper and sketch out the stack frames and control variables for each of your tasks.
  • Consider where those frames exist in memory and how you would initialise the tasks & control variables.
  • Colour in blocks representing the program counter pushed onto the stack by the ISR.
  • Colour in blocks representing all the pushes and pops your ISR performs and where SP sits.
  • Now you can visualise how you can perform the context switch.

 

 

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 1

N.Winterbottom wrote:
It's probably time now to step away from the computer.
+1000

 

As I said elsewhere...

 

https://www.avrfreaks.net/commen...

 

I'd also reiterate https://www.avrfreaks.net/commen... (#28) in this thread. That sequence of pages already has all the pretty diagrams ready sketched out. They are as relevant for the implementation of any OS as they are for FreeRTOS.