Tiny Real-Time Kernel and AVR Studio 6

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

I tried to re-organize Tiny Real-Time Kernel  in AVR Studio 6 since previous portion is pretty nasty - "include "*.c" directly",  but I found some problems relevant to the GCC compiler, I fixed some bugs, here comes the main problem: Context Switch cannot be performed.

 

Current HW platform: ATmega1284p.   RAM: 16384Bytes (16KB)

 

Two pieces of code is posted below (CreateTask) and the scheduling ISR (TMER1_COMPA).

 

ISR(TIMER1_COMPA_vect) {

    uint8_t running, oldrunning;
    struct task *t;
    uint8_t i;
    uint32_t now;
    uint32_t nextHit;
    int32_t timeleft;
    
    TIMSK1 = 0 ; //&= ~(1<<OCIE1A); // turn off output compare 1A ISR
    //PORTC = ~PORTC ;
    nextHit = 0x7FFFFFFF;
    oldrunning = kernel.running;
    running = 0;

    if (TIFR1 & (1<<TOV1)) {
        ++kernel.cycles;
        TIFR1 |= (1<<TOV1) ;
    }

    // Read clock

    now = (kernel.cycles << 16) + TCNT1;

    // Release tasks from TimeQ and determine new running task

    for (i=1; i <= kernel.nbrOfTasks; i++) {
        t = &kernel.tasks[i];
        if (t->state == TIMEQ) {
            if (t->release <= now) {
                t->state = READYQ;
                } else if (t->release < nextHit) {
                nextHit = t->release;
            }
        }
        if (t->state == READYQ) {
            if (t->deadline < kernel.tasks[running].deadline) {
                running = i;
            }
        }
    }

    if (running != oldrunning) { // perform context switch?

        // store old context
        t = &kernel.tasks[oldrunning];
        t->spl = SPL;
        t->sph = SPH;

        // load new context
        t = &kernel.tasks[running];
        SPH = t->sph;
        SPL = t->spl;

        kernel.running = running;

    }

    kernel.nextHit = nextHit;

    now = (kernel.cycles << 16) + TCNT1;
    timeleft = (int32_t)nextHit - (int32_t)now;
    if (timeleft < 4) {
        timeleft = 4;
    }

    if ((unsigned long)TCNT1 + timeleft < 65536) {
        OCR1A = TCNT1 + timeleft;
        } else if (TCNT1 < 65536 - 4) {
        OCR1A = 0x0000;
        } else {
        OCR1A = 4;
    }

    TIMSK1 = (1 << OCIE1A);
//    Drv_LED_Toggle();
}


void trtCreateTask(void (*fun)(void*), uint16_t stacksize, uint32_t release, uint32_t deadline, void *args) {

    volatile uint8_t *stk_ptr;
    trt_tast_t *t;
    int i;
    
    cli(); // turn off interrupts

    ++kernel.nbrOfTasks;

    stk_ptr = kernel.memptr;
    kernel.memptr -= stacksize;  // decrease free mem ptr

    // initialize stack
    *stk_ptr-- = lo8(fun);       // store PC(lo)
    
    *stk_ptr-- = hi8(fun);       // store PC(hi)
    for (i=0; i<26; i++)    //WAS -- for (i=0; i<25; i++)
    *stk_ptr-- = 0x00;         // store SREG,r0-r1,r3-r23

    // Save args in r24-25 (input arguments stored in these registers)
    *stk_ptr-- = lo8(args);
    *stk_ptr-- = hi8(args);

    for (i=0; i<6; i++)
    *stk_ptr-- = 0x00;         // store r26-r31

    t = &kernel.tasks[kernel.nbrOfTasks];

    t->release = release;
    t->deadline = deadline;
    t->state = TIMEQ;

    t->spl = lo8(stk_ptr);       // store stack pointer
    t->sph = hi8(stk_ptr);
    
    // call interrupt handler to schedule
    TIMER1_COMPA_vect();
//    sei();
}

 

I simulate it in AVR Studio 6, and it cannot perform context switch - I just create a simple task which toggles the LED...

 

I do the debug only to find that when the first Context-Switch happens, the stack pointer is indeed modified, but after "RETI", the PC fails to get the pointer which points to the task I created. 

This topic has a solution.

Fighting until death approaches.

Last Edited: Sat. Oct 25, 2014 - 07:46 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

AVR GCC version of AVR Studio 4.17 and AVR Studio 6 are different. 

 

Still debug, any suggestion is welcome...

 

 

Fighting until death approaches.

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

Without special compilers options, the ISRs have register save/restore silently emitted by the compiler and RETI is not manually coded.

 

A short read of the code posted here... yikes. Too much time in the ISR by far.

Bad code like this:

if ((unsigned long)TCNT1 + timeleft < 65536) {
        OCR1A = TCNT1 + timeleft;
        } else if (TCNT1 < 65536 - 4) {
        OCR1A = 0x0000;
        } else {
        OCR1A = 4;

 

blocking interrupts far too long, in this trtCreateTask()

 

 

I'd take a pass on this code base and use FreeRTOS or ChibiOS

Or SEOS

 

 

Last Edited: Sat. Oct 25, 2014 - 06:05 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Thanks for your response, but I need to figure out why it cannot perform task scheduling in AVR Studio 6... also a kind of learning...

 

Any guidance? (:

Fighting until death approaches.

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

Yes, this method is pretty nasty, and after I figure out the problem, I will change it...

Fighting until death approaches.

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

I need to figure out why it cannot perform task scheduling in AVR Studio 6.

Do you know what a  "stack frame" is?  Have you looked at the code for the stack frame for that ISR?  Is the ISR code always deterministic w.r.t. push/pop count?

 

Have you even told us which AVR model you are building for, and whether it has enough SRAM for your brute-force approach?

 

 

You can put lipstick on a pig, but it is still a pig.

I've never met a pig I didn't like, as long as you have some salt and pepper.

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

theusch wrote:

I need to figure out why it cannot perform task scheduling in AVR Studio 6.

Do you know what a  "stack frame" is?  Have you looked at the code for the stack frame for that ISR?  Is the ISR code always deterministic w.r.t. push/pop count?

 

Have you even told us which AVR model you are building for, and whether it has enough SRAM for your brute-force approach?

 

 

 

Thanks for your suggestion, I modified the post. 

 

I know a little about "stack frame", the stack frame at the top of the stack is for the currently executing routine. 

 

About push / pop count, since this kernel never restore context by itself, it depends on the compiler, and I think it is the problem here....

Fighting until death approaches.

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

ChristianLeo2014 wrote:
Current HW platform: ATmega1284p.

ChristianLeo2014 wrote:
AVR GCC version of AVR Studio 4.17 and AVR Studio 6 are different.

Though the following does not solve your problem it's an alternate TinyRealTime for mega644 and mega1284

Cornell University ECE4760
A preemptive kernel for Atmel Mega1284 microcontrollers

http://people.ece.cornell.edu/land/courses/ece4760/TinyRealTime/index.html

Tiny Real Time (TRT), a real-time kernel

The operating system I will describe, called TinyRealTime (TRT) was written by Dan Henriksson and Anton Cervin (technical report).

...

TRT was written in GCC for the Mega8. Porting to the Mega1284 was easy (rename timer registers, add two bytes to the stack). It could run on any Atmel AVR processor with a 16-bit timer and sufficient RAM.

...

NOTE: This code has been verified on AVRstudio 4.15 using the WinAVR-20100110 avr-gcc.exe compiler. It may not compile on other versions. The symptom will be endless resets.

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

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

gchapman wrote:

 

ChristianLeo2014 wrote:

Current HW platform: ATmega1284p.

 

 

ChristianLeo2014 wrote:

AVR GCC version of AVR Studio 4.17 and AVR Studio 6 are different.

 

Though the following does not solve your problem it's an alternate TinyRealTime for mega644 and mega1284

Cornell University ECE4760
A preemptive kernel for Atmel Mega1284 microcontrollers

http://people.ece.cornell.edu/land/courses/ece4760/TinyRealTime/index.html

Tiny Real Time (TRT), a real-time kernel

The operating system I will describe, called TinyRealTime (TRT) was written by Dan Henriksson and Anton Cervin (technical report).

...

TRT was written in GCC for the Mega8. Porting to the Mega1284 was easy (rename timer registers, add two bytes to the stack). It could run on any Atmel AVR processor with a 16-bit timer and sufficient RAM.

...

NOTE: This code has been verified on AVRstudio 4.15 using the WinAVR-20100110 avr-gcc.exe compiler. It may not compile on other versions. The symptom will be endless resets.

 

Thanks for your response, I fixed the "RESET" problem you mentioned. It's the default compiler utilization, I disable it, and it stop resetting itself.

 

I also configure AVR Studio 6 to use WinAVR-20100110, the explanation you mentioned was superficial, for either AVR Studio 6 or AVR Studio 4, GCC is a plugin, which means if I use WinAVR-20100110 as the compiler, it should work.

 

I know it will work with 4.17 & WinAVR-20100110, I just want to know why it cannot work in AVR Studio 6 & WinAVR-20100110.

 

Fighting until death approaches.

Last Edited: Sun. Oct 26, 2014 - 12:20 AM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

About push / pop count, since this kernel never restore context by itself, it depends on the compiler,

???  Anyone that relies on the compiler to "restore context"  (and how exactly can a >>compiler<< know how to handle >>your<< TCB and etc.?)  in a pre-emptive RTOS is due for situations like yours.

 

Other than as a basic learning exercise, (IMO) any pre-emptive RTOS on an AVR8 is a fools's errand.  Yes, the big ones, Mega640 family, have enough SRAM to do at least some stuff.  But an app of that class, 64K+ of code, will have a fairly large number of tasks.  And if you combine operation to keep the number of tasks down, then why are you using a pre-emptive RTOS in the first place?

 

And if it is indeed a learning exercise, then study the generated code for the ISR, and trace through it with the ASM or combined view.

 

 

You can put lipstick on a pig, but it is still a pig.

I've never met a pig I didn't like, as long as you have some salt and pepper.

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

theusch wrote:

About push / pop count, since this kernel never restore context by itself, it depends on the compiler,

???  Anyone that relies on the compiler to "restore context"  (and how exactly can a >>compiler<< know how to handle >>your<< TCB and etc.?)  in a pre-emptive RTOS is due for situations like yours.

 

Other than as a basic learning exercise, (IMO) any pre-emptive RTOS on an AVR8 is a fools's errand.  Yes, the big ones, Mega640 family, have enough SRAM to do at least some stuff.  But an app of that class, 64K+ of code, will have a fairly large number of tasks.  And if you combine operation to keep the number of tasks down, then why are you using a pre-emptive RTOS in the first place?

 

And if it is indeed a learning exercise, then study the generated code for the ISR, and trace through it with the ASM or combined view.

 

 

 

Thanks for your helpful response. This kernel relies "compiler" to restore context, I mean, it actually depends on the TIMER1 COMPA ISR. When ISR finishes, it will pop stack sequentially before "RETI". I actually means this. And I think this interrupt behavior depends on compiler.

 

This case indeed is a learning exercise, I found the problem by checking the disassembly, I am now just trying to find a solution (compiler options) to fix this, or rewrite it directly.

Fighting until death approaches.

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

Have a read of this:

 

http://www.freertos.org/implemen...

 

(keep following the "Next:" links at the bottom of each page).

 

That tells you how another RTOS (perhaps the most famous/widely used open source one?) is implemented. It's true that it's talking about an old version of GCC but a lot of what it says is still valid.

 

Notice how they use inline Asm for the context store/restore? That's because this is close to impossible to achieve (deterministically) in C alone.

 

Personally I wouldn't even use inline asm but would but the context switching stuff in a .S file as it's much easier to write/control assembler in that way.

 

I agree with a lot of what Lee says - a pre-emptive OS (that requires TCBs) is not really a practical proposition in AVrs with less than 2K of RAM. I once did a small test with FreeRTOS in a mega168 that has 1K of SRAM. I could get 5 (extremely simple!) tasks running but that was about the limit. In reality, ina 1K (RAM) AVR I think 3 tasks would be more practical. If you are going to blow the CPU overhead on an RTOS in the first place what is to be gained if you can only run 3 (or even fewer) tasks? You might as well just write a program with a main() and a couple of ISR()s! RTOS only really come in to play when you maybe have 10 or more things to run concurrently and you have the RAM to accomodate them.

 

If you visit the Tutorial forum you will find an article by "Kartman" that propoes a co-operative multi-task structure that is far more relevant for low resource AVRs.

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

Very useful comments! :)  Thanks for your reply! I am on Lee's side too, but it is also a good learning chance to fix this problem.

 

This TRT Kernel's weakness is that it depends on compiler to do the stack PUSH/POP operations which cannot be portable.

 

Fighting until death approaches.

This reply has been marked as the solution. 
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

This TRT Kernel's weakness is that it depends on compiler to do the stack PUSH/POP operations which cannot be portable.

I think you are going to find the tight link to the compiler (even to a specific version of the specific compiler) with any pre-emptive RTOS that attempts to allow the user to write their task code in C. The RTOS must "know" things about how the compiler will be handling things - like the ABI it uses and even the auto-generated prologue of any ISRs (unless, like GCC it has the equivalent of "naked").

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

clawson wrote:

This TRT Kernel's weakness is that it depends on compiler to do the stack PUSH/POP operations which cannot be portable.

I think you are going to find the tight link to the compiler (even to a specific version of the specific compiler) with any pre-emptive RTOS that attempts to allow the user to write their task code in C. The RTOS must "know" things about how the compiler will be handling things - like the ABI it uses and even the auto-generated prologue of any ISRs (unless, like GCC it has the equivalent of "naked").

 

Correct!!!!!!!

I got help from my mentor, will post it soon. :)

Fighting until death approaches.

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

 

The solution to this problem is to make the scheduler ISR to be naked, and determine the AVR_ARCH which I forgot when I was trying to fix it.

 

ISR(TIMER1_COMPA_vect, ISR_NAKED) {

    uint8_t running, oldrunning;
    struct task *t;
    uint8_t i;
    uint32_t now;
    uint32_t nextHit;
    int32_t timeleft;
    
    OS_CONTEXT_SAVE();
    
    TIMSK1 = 0 ; //&= ~(1<<OCIE1A); // turn off output compare 1A ISR
    //PORTC = ~PORTC ;
    nextHit = 0x7FFFFFFF;
    oldrunning = kernel.running;
    running = 0;

    if (TIFR1 & (1<<TOV1)) {
        ++kernel.cycles;
        TIFR1 |= (1<<TOV1) ;
    }

    // Read clock

    now = (kernel.cycles << 16) + TCNT1;

    // Release tasks from TimeQ and determine new running task

    for (i=1; i <= kernel.nbrOfTasks; i++) {
        t = &kernel.tasks[i];
        if (t->state == TIMEQ) {
            if (t->release <= now) {
                t->state = READYQ;
            }
            else if (t->release < nextHit) {
                nextHit = t->release;
            }
        }
        if (t->state == READYQ) {
            if (t->deadline < kernel.tasks[running].deadline) {
                running = i;
            }
        }
    }

    #ifdef TRACE_PORT
    // clear low 3 bits and set them to running task
    TRACE_PORT = (TRACE_PORT & 0xf8) | running ;
    #endif

    if (running != oldrunning) { // perform context switch?

        // store old context
        t = &kernel.tasks[oldrunning];
        t->spl = SPL;
        t->sph = SPH;

        // load new context
        t = &kernel.tasks[running];
        SPH = t->sph;
        SPL = t->spl;

        kernel.running = running;
    }

    kernel.nextHit = nextHit;

    now = (kernel.cycles << 16) + TCNT1;
    timeleft = (int32_t)nextHit - (int32_t)now;
    if (timeleft < 4) {
        timeleft = 4;
    }

    if ((unsigned long)TCNT1 + timeleft < 65536) {
        OCR1A = TCNT1 + timeleft;
        } else if (TCNT1 < 65536 - 4) {
        OCR1A = 0x0000;
        } else {
        OCR1A = 4;
    }

    TIMSK1 = (1<<OCIE1A);
    
    OS_CONTEXT_RESTORE();
}

 


 

if (__AVR_ARCH__) == 51
    #define OS_CONTEXT_ARCH_SAVE()    {    \
        asm("in r0, 0x3b");    /*RAMPZ*/    \
        asm("push r0");                    \
    }
    
    #define OS_CONTEXT_ARCH_RESTORE() {    \
        asm("pop r0");                    \
        asm("out 0x3b, r0");    /*RAMPZ*/    \
    }
#endif

#define OS_CONTEXT_SAVE() {            \
    asm("push r1");                    \
    asm("push r0");                    \
    asm("in r0, 0x3f");    /*SREG*/    \
    asm("push r0");                    \
    asm("eor r1, r1");                \
    OS_CONTEXT_ARCH_SAVE();         \
    asm("push r2");                    \
    asm("push r3");                    \
    asm("push r4");                    \
    asm("push r5");                    \
    asm("push r6");                    \
    asm("push r7");                    \
    asm("push r8");                    \
    asm("push r9");                    \
    asm("push r10");                \
    asm("push r11");                \
    asm("push r12");                \
    asm("push r13");                \
    asm("push r14");                \
    asm("push r15");                \
    asm("push r16");                \
    asm("push r17");                \
    asm("push r18");                \
    asm("push r19");                \
    asm("push r20");                \
    asm("push r21");                \
    asm("push r22");                \
    asm("push r23");                \
    asm("push r24");                \
    asm("push r25");                \
    asm("push r26");                \
    asm("push r27");                \
    asm("push r28");                \
    asm("push r29");                \
    asm("push r30");                \
    asm("push r31");                \
}

#define OS_CONTEXT_RESTORE() {              \
    asm("pop r31");                       \
    asm("pop r30");                       \
    asm("pop r29");                       \
    asm("pop r28");                       \
    asm("pop r27");                       \
    asm("pop r26");                       \
    asm("pop r25");                       \
    asm("pop r24");                       \
    asm("pop r23");                       \
    asm("pop r22");                       \
    asm("pop r21");                       \
    asm("pop r20");                       \
    asm("pop r19");                       \
    asm("pop r18");                       \
    asm("pop r17");                       \
    asm("pop r16");                       \
    asm("pop r15");                       \
    asm("pop r14");                       \
    asm("pop r13");                       \
    asm("pop r12");                       \
    asm("pop r11");                       \
    asm("pop r10");                       \
    asm("pop r9");                        \
    asm("pop r8");                        \
    asm("pop r7");                        \
    asm("pop r6");                        \
    asm("pop r5");                        \
    asm("pop r4");                        \
    asm("pop r3");                        \
    asm("pop r2");                        \
    OS_CONTEXT_ARCH_RESTORE();              \
    asm("pop r0");                        \
    asm("out 0x3f, r0");    /*SREG*/      \
    asm("pop r0");                        \
    asm("pop r1");                        \
    asm("reti");                          \
}

 

Fighting until death approaches.

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

You can't have locals in a naked routine!! 

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

Indeed.  The only safe code to have in a naked routine is ASM.  Any use of C code will only work 'by accident' and requires knowledge of implementation dependent details.

"Experience is what enables you to recognise a mistake the second time you make it."

"Good judgement comes from experience.  Experience comes from bad judgement."

"When you hear hoofbeats, think horses, not unicorns."

"Fast.  Cheap.  Good.  Pick two."

"Read a lot.  Write a lot."

"We see a lot of arses on handlebars around here." - [J Ekdahl]

 

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

clawson wrote:

You can't have locals in a naked routine!! 

 

Gosh, naked ISR has no stack frame set-up... my fault....  I need to further dig it why this code can work... weird...

Fighting until death approaches.

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

Do you remember when I said:

Notice how they use inline Asm for the context store/restore? That's because this is close to impossible to achieve (deterministically) in C alone.

 

Personally I wouldn't even use inline asm but would but the context switching stuff in a .S file as it's much easier to write/control assembler in that way.

Nothing about that has changed. You just cannot do what you are attempting in C alone. Even if you did achieve it the next time the compiler was updated you might find the code generation model changed subtly that then broke the code.

 

What merit do you see in implementing OS_CONTEXT_SAVE in inline asm() and then invoking that from C? If you are going to write Asm why don't you simply put TIMER1_COMPA_vect as a function in a separate .S and implement the whole thing in Asm so you have complete control over the register usage?