Starting from:

$25

CPEN412 -  Microcomputer Systems Design - Assignment 6A - Solved

 To gain an appreciation of what is required to “port” an operating system such as Micrium’s uC/OS II to a small microcontroller, in particular, modifying the processor and board dependent software and features of the operating system to run in that environment.

 

Overview

This is actually quite a simple assignment, as most of the work is already done within the IDE68k ‘C’ compiler (i.e. it comes with a bunch of files specific to the 68k processor) and by your instructor (who wrote the board specific features related to the DE1). 

All you have to do is understand the background and files involved in this port and then write a simple multi-threaded application to run on the 68k. It should not take more than an hour or two, but obviously that depends how much reading you choose to do as there is wealth of data and user manuals on the web, as a quick search will reveal. 

 

Introduction

The Micrium OS-II operating system (https://www.micrium.com/ ) is a very popular, real-time embedded OS that has been ported to wide range of microprocessors and microcontrollers. These include the 80x86 (PC) architecture, ARM, NIOS, 68k family as well as small 8 bit microcontrollers such as 6811/6812 etc. If you took CPEN 391 last year or are taking it this year, you might have noticed that Eclipse had an option to generate a uC/OS-II project for the NIOS processor, although we didn’t make use of it (see Fig 1.0 below)

 

 

Fig 1: Eclipse showing option to create a uC/OS II project

Micrium’s uC/OS-II started off as a personal project by Jean Labrosse and was intended for highly embedded applications, e.g. robotised assembly lines, aerospace, medical devices, engine management systems in cars etc. Now that we have entered the era of the “Internet-of-things”, Micrium’s OS has taken on a new level of significance. We can now design systems around small 8/16 bit microcontrollers with limited memory footprints and put a real-time OS into Rom alongside our application. This will have the potential to connect to the internet and support multi-threading. Depending upong which features you intend to use (governed by what files you compile), uC/OS-II takes between 4 and 24 kbytes of memory (yes it’s small)

 

uC/OS-II is not an OS that you would instantly recognise, as there is no “boot” process from disk. In fact there is no default file system at all, nor any kind of GUI or Shell with which you could interact. Instead, uC/OS-II contains a functioning OS Kernel which handles prioritised scheduling (through timeslicing) between multiple threads in a single core system.

 

As such it allows for the creation and control or prioritised threads, synchronisation between them in the form of mutex’s, semaphores and events, and inter-thread communication in the form of shared variables and memory, mailboxes and message queues. Shared variables also facilitates the creation of pipelines as a kind of FIFO communication buffer between threads.

 

All this is available for free and can be used without a license. However some of the “add-ons” for the OS require that you pay for a license, e.g. some device drivers, TCP/IP networking, Canbus, USB, FAT32 File system support etc.

 

If you are a Computer Engineer, you probably learned about many of these concepts in your operating systems course CPEN 331. If you are an Electrical Engineering, you might have taken CPEN 333 (as an elective) and learned about them there. 

 

You can read about the OS by visiting Micrium’s web site (https://www.micrium.com/ ) or via some of the lecture notes and manuals posted on Canvas. These cover everything from basic concepts, i.e. how the Kernel works and its implementation in ‘C’, right up to dicussing the API calls that your application can make to use features of the OS, e.g. create threads etc.

 

When you installed IDE68k at the start of this course, it came with an implementation of the uC/OS-II Operating system source files that were installed into the C:\IDE68K\uCOSII folder (see figure 2.0 below). That is, it came with a bunch of ‘C’ code and assembly language files that you could add to your 68k projects to give you the functionality discussed above, however this was set up to run in a 68k simulator that came as part of the IDE. We want to adapt that to run on a real board, i.e. the DE1.

 

Fig 2: Default files that came with IDE68k

 

Because most of the OS Kernel was written in ‘C’ (just like UNIX) it is relatively easy to recompile the OS to work on a range of different processors. This fact alone accounts for much of its popularity. Many of the source files above need NO modification at all, but some do, as an OS needs to handle things like context switching between threads, which involves stacking and unstacking CPU registers as the processor switches between tasks preserving and restoring the state of the processor registers between task swaps. This implies some files need to be written in assembly language. 

 

It also requires that we write code to handle the “task swap” interrupts generated by a real-time clock and possibly other hardware devices in the system. This introduces some elements of processor dependence into the OS code and thus those parts of the kernel have to be written in Assembly language, although it’s a fairly small amount of code.

 

Micrium have done an excellent job of isolating the processor/board dependent code into separate ‘C’ and Assembly language files and then documented what that code has to do in terms of a general pseudocode description. Making changes to those processor/board dependent files is what is meant by “porting” an OS to a particular processor/board. 

 

You can see these processor dependent/independent files below in Figure 3.0 . Page 350 in the Micrium “uCOS/II User Manual” (see manual on Canvas) describes the porting process in detail and we will describe it here and show what has been done to port it to the 68k and the DE1. 

 

 

 

Fig 3.0: Processor dependent (specific) and independent files for uC/OS II

 

 

Fig 3.0 above, taken from the uC/OS II user manual, shows the files required for a uC/OS II “port”. You can cross reference these with the files that were installed with the IDE68k ‘C’ compiler (see Fig 2.0). You are invited to explore any of these “.C” files to gain a deeper unstanding of the OS kernel and how it works, but it’s not necessary for this assignment. 

 

Porting uC/OS-II to the 68k and DE1 board

In the following sections you will find a discussion of the relevant parts of the Micrium user manual describing the porting of the OS to a CPU and Board. In our case the 68k and DE1 board. 

 

You are encouraged to look at the descriptions here and in the Micrium User Manual starting at Page 350 and then relate the discussion to the files that have been posted on Canvas. The files on Canvas should replace the default files that came with the IDE68k installation in the folder C:\IDE68k\uCOSII as they contain code related to a DE1 port

 

The OS_CPU_H header file (Page 356 – User Manual) 

The OS_CPU.H header file is supposed to define new compiler independent data types as well as macros to handle the entry and exit of critical sections of code (i.e. code where a timeslice/context switch should be disabled). The code will involve disabling and reenabling interrupts and as such depends on the host CPU – the 68k.

 

The section from the user manual is given below. Our 68k does not have a floating point co-processor so we do not need to worry about the “fp” reference.

 

 

The code to implement this file is given on Canvas and again below, it uses the “_word” macro in IDE68k assembler to embed 68k machine code op-code (i.e. machine code) directly into the file, to enable/disable interrupts within the 68k by masking/unmasking the appropriate bits in the 68k Status Register.

 

/*

*********************************************************************************************************

*                                               uC/OS-II

*                                        The Real-Time Kernel

*

*                            (c) Copyright 2000, Jean J. Labrosse, Weston, FL

*                                          All Rights Reserved

*

*                                          M68000 Specific code

*                                              IDE68K v 2.2

*

* File         : OS_CPU.H

* By           : Jean J. Labrosse, Peter J. Fondse

*********************************************************************************************************

*/

 

/*

*********************************************************************************************************

*                                           REVISION HISTORY

*

* $Log$

*

*********************************************************************************************************

*/

 

/*$PAGE*/

/*

*********************************************************************************************************

*                                              DATA TYPES

*********************************************************************************************************

*/

 

typedef unsigned char  BOOLEAN;

typedef unsigned char  INT8U;                    /* Unsigned  8 bit quantity                           */

typedef signed   char  INT8S;                    /* Signed    8 bit quantity                           */

typedef unsigned short INT16U;                   /* Unsigned 16 bit quantity                           */

typedef signed   short INT16S;                   /* Signed   16 bit quantity                           */

typedef unsigned int   INT32U;                   /* Unsigned 32 bit quantity                           */

typedef signed   int   INT32S;                   /* Signed   32 bit quantity                           */

typedef float          FP32;                     /* Single precision floating point                    */

typedef double         FP64;                     /* Double precision floating point                    */

 

#define BYTE           INT8S                     /* Define data types for backward compatibility ...   */

#define UBYTE          INT8U                     /* ... to uC/OS V1.xx                                 */

#define WORD           INT16S

#define UWORD          INT16U

#define LONG           INT32S

#define ULONG          INT32U

 

typedef unsigned short OS_STK;                   /* Each stack entry is 16-bit wide                    */

 

/*

*********************************************************************************************************

*                                           Motorola 68000

*

* Method #1:  Disable/Enable interrupts using simple instructions.  After critical section, interrupts

*             will be enabled even if they were disabled before entering the critical section.

*

* Method #2:  Disable/Enable interrupts by preserving the state of interrupts.  In other words, if

*             interrupts were disabled before entering the critical section, they will be disabled when

*             leaving the critical section.

*********************************************************************************************************

*/

#define  OS_CRITICAL_METHOD    2

 

#if      OS_CRITICAL_METHOD == 1

#define  OS_ENTER_CRITICAL()  _word(0x007C); _word(0x0700)                /* asm("  ORI   #$0700,SR\n")                 */

#define  OS_EXIT_CRITICAL()   _word(0x027C); _word(0xF800)                /* asm("  AND   #$0F800,SR\n")                */

#endif

 

#if      OS_CRITICAL_METHOD == 2

#define  OS_ENTER_CRITICAL()  _word(0x40E7); _word(0x007C); _word(0x0700) /* asm("  MOVE  SR,-(A7)\n  ORI #$0700,SR\n")  */

#define  OS_EXIT_CRITICAL()   _word(0x46DF)                               /* asm("  MOVE  (A7)+,SR\n")                   */

#endif

 

#define  CPU_INT_DIS()        _word(0x007C); _word(0x0700)                /* asm("  ORI   #$0700,SR\n"                   */

#define  CPU_INT_EN()         _word(0x027C); _word(0xF800)                /* asm("  AND   #$0F800,SR\n")                 */

 

#define  OS_TASK_SW()         _trap(0)                                    /* asm("  TRAP  #0\n")                         */

 

#define  OS_STK_GROWTH        1                                           /* Define CPUs stack growth: 1 = Down, 0 = Up   */

 

#define  OS_INITIAL_SR        0x2000                                      /* Supervisor mode, all interrupts enabled     */

 

#define  OS_TRAP_NBR          0                                           /* OSCtxSw() invoked through TRAP #0            */

 

void OSVectSet(INT8U vect, void (*addr)(void));

void *OSVectGet(INT8U vect);

void OSIntExit68K(void);

void OSStartHighRdy(void);

void OSIntCtxSw(void);

void OSCtxSw(void);

void OSFPRestore(void *);

void OSFPSave(void *);

void OSTickISR(void);

 

 

 

The OS_CPU_C.C file (Page 364 – user manual)

 

A μC/OS-II port requires that you write ten (10) fairly simple ‘C’ code functions:

 

OSTaskStkInit()
OSTaskCreateHook()
OSTaskDelHook()
OSTaskSwHook()
OSTaskIdleHook()
OSTaskStatHook()
OSTimeTickHook()
OSInitHookBegin()
OSInitHookEnd()
OSTCBInitHook()
 

The only required function is OSTaskStkInit(). The other nine functions are “Hook” functions that must be defined but may not need any code, i.e. just empty stub functions. These 9 hook functions exist so that you may optionally intercept critical parts of the operating system and direct it to perform some task of your own before or after the critical OS operation. That is to say, the Kernel will call these Hook functions and give you the opportunity to “do something” (if you wish).

 

OSTaskStkInit()

This function is the only function here that we have to modify. It is called by the kernel functions OSTaskCreate() and OSTaskCreateExt() during the creation of a new task/thread. Its purpose is to initialize the CPU (i.e. the 68k’s) stack frame for the new task/thread to make it appear as if an interrupt had just occurred and all the processor registers were already pushed onto that stack. The pseudo code for OSTaskStkInit() is shown in listing 13.8. The reason for this is that in order to start the task, the 68k will pull these registers off the stack (including the Program counter) with a return from exception instruction (RTE) and this will cause the 68k to jump to the task code and execute it.

 

 

 

Port Code for OSTaskStkInit() for 68k

 

OS_STK *OSTaskStkInit(void (*task)(void *pd), void *pdata, OS_STK *ptos, INT16U opt)

{

    INT32U  *pstk32;

    INT16U  *pstk16;

 

 

    opt       = opt;                                                                /* 'opt' is not used, prevents warning            */

                                                                                             /* Load stack pointer and align on 32-bit bound  */

    pstk32    = (INT32U *)((INT32U)ptos & 0xFFFFFFFCL);

                                                                                              /* -- SIMULATE CALL TO FUNCTION WITH ARGUMENT -- */

    *--pstk32 = (INT32U)pdata;                                       /*    pdata                                      */

    *--pstk32 = (INT32U)task;                                         /*    Task return address  - for program counter                       */

                                                                                             /* ------ SIMULATE INTERRUPT STACK FRAME ------- */

    *--pstk32 = (INT32U)task;                                         /*    Task return address                        */

    pstk16    = (INT16U *)pstk32;                                   /* Switch to 16-bit wide stack                   */

    *--pstk16 = (INT16U)OS_INITIAL_SR;                     /*    Initial Status Register value for the task */

    pstk32    = (INT32U *)pstk16;                                   /* Switch to 32-bit wide stack                   */

 

/* ------- SAVE ALL PROCESSOR REGISTERS on Task/Thread Stack-------- */



    *--pstk32 = (INT32U)0x00A600A6L;                        /* Register A6                                   */

    *--pstk32 = (INT32U)0x00A500A5L;                       /* Register A5                                   */

    *--pstk32 = (INT32U)0x00A400A4L;                        /* Register A4                                   */

    *--pstk32 = (INT32U)0x00A300A3L;                       /* Register A3                                   */

    *--pstk32 = (INT32U)0x00A200A2L;                        /* Register A2                                   */

    *--pstk32 = (INT32U)0x00A100A1L;                       /* Register A1                                   */

    *--pstk32 = (INT32U)0x00A000A0L;                        /* Register A0                                   */

    *--pstk32 = (INT32U)0x00D700D7L;                        /* Register D7                                   */

    *--pstk32 = (INT32U)0x00D600D6L;                       /* Register D6                                   */

    *--pstk32 = (INT32U)0x00D500D5L;                       /* Register D5                                   */

    *--pstk32 = (INT32U)0x00D400D4L;                        /* Register D4                                   */

    *--pstk32 = (INT32U)0x00D300D3L;                       /* Register D3                                   */

    *--pstk32 = (INT32U)0x00D200D2L;                       /* Register D2                                   */

    *--pstk32 = (INT32U)0x00D100D1L;                       /* Register D1                                   */

    *--pstk32 = (INT32U)0x00D000D0L;                       /* Register D0                                   */

    return ((OS_STK *)pstk32);                                        /* Return pointer to new top-of-stack            */

}

 

 

 

The OS_CPU_A.ASM file (Page 372 – User Manual)

 

A μC/OS-II port requires that you write four assembly language functions specific to your CPU:

 

OSStartHighRdy()
OSCtxSw()
OSTickISR()
OSIntCtxSw()


 

The Function OSStartHighRdy()

 

This function is called by OSStart() (a function your application calls to start the operating system – see listing at end of this document) to start the highest priority task that is able to run. It pulls the 68k’s registers off the stack and jumps to the code. The pseudo-code for this function is shown in Listing 13.12. This needs to be converted to assembly language.

 

 

 

The implementation of OSStartHighRdy() in 68k assembly language is given below

 

 

 

 

 

_OSStartHighRdy:

        jsr       _OSTaskSwHook                              ; Invoke user defined context switch hook

        addq.b    #1,_OSRunning                           ; Indicate that we are multitasking

        move.l    _OSTCBHighRdy,A0                    ; Point register A0 to Task control block (TCB)

; of highest priority task ready to run

        move.l    (A0),A7                                          ; Get the stack pointer of the task to resume into reg A7

        movem.l   (A7)+,A0-A6/D0-D7                 ; restore the CPU registers from the Task control block (TCB)

        rte                                                                   ; Return from Exception/Interrupt and hence reload

 

 

The Function OSCtxSw()

 

Within uC/OS-II a task-level “context switch” occurs whenever the application or OS executes a software interrupt instruction (a TRAP #0 assembly language instruction on the 68k and in this Port). 

 

Usually this is in response to some kind of “blocking system call” such as a thread making an OS call to wait or “pend” on a blocking semaphore. At such a point, the task will be blocked and cannot proceed, so the OS has to save the state (context) of the task (i.e. the 68k registers) and figure out which task to run next. 

 

This function is thus called by the OS to perform the context switch which is highly CPU dependent. It must save the state of the current task (i.e. the CPU registers the task is using), locate the next task to run from the task control block or TCB  which is a list of running tasks, and then restore the next tasks state (CPU registers) before returning to run that task.

 

The pseudo code for OSCtxSw() is shown in Listing 13.13. This code must be written in assembly language because you cannot access CPU registers directly from ‘C’. Note that CPU interrupts are disabled during OSCtxSw() and also during execution of the user-definable function OSTaskSwHook(). 

 

When OSCtxSw() is invoked, it is assumed that only the processors program counter (PC) and status register (in the case of the 68k) are pushed onto the stack by the software interrupt instruction which is invoked by the OS_TASK_SW() macro in ‘C’.

 

 

 

The implementation of the function OSCtxSw() is given below in 68k Assembly language

 

_OSCtxSw:

        movem.l   A0-A6/D0-D7,-(A7)                                  ; Save the 68k registers for the current task

        move.l    _OSTCBCur,A0                                             ; get the TCB into A0

        move.l    A7,(A0)                                                          ; Save stack pointer A7 in the suspended task TCB

        jsr       _OSTaskSwHook                                              ; Invoke user defined context switch “hook” fn

        move.b    _OSPrioHighRdy,_OSPrioCur                  ; set OSPrioCur = OSPrioHighRdy

        move.l    _OSTCBHighRdy,A0                                    ; get TCB of task to resume into A0

        move.l    A0,_OSTCBCur                                             ; set OSTCBCur  = OSTCBHighRdy

        move.l    (A0),A7                                                           ; Get the stack pointer of the task to resume

        movem.l   (A7)+,A0-A6/D0-D7                                  ; Restore the CPU registers from the stack

        rte                                                                                   ; return from exception and run task

 

 

The Function OSTickISR()

 

This is a very important function. μC/OS-II requires a timer that generates the periodic “Tick” that causes the OS to swap between tasks at regular intervals, i.e. give the illusion of concurrency through time slicing. This “tick” is in the form of an interrupt from a hardware timer. A periodic tick generally occurs between 10 and 100 Hz. The timer also allows the OS to suspend tasks for a programmed period of time, e.g. 30 ticks etc 

 

To accomplish this, the 68k system designed on the DE1 board has 8 separate timers (see image below). They are all combined into 1 active low interrupt that is connected to IRQ Level 3 on the 68k. You might want to revisit Lecture 17 - IO Interfacing and Interrupts.pptx to refresh yourself on how the 68k deals with interrupts.

 

  

 

 

 

We can use Timer 1 for this task, i.e. program it to produce interrupts every 10 - 100Hz. 

 

The function OSTickISR() is thus the interrupt service routine for the Timer and has to clear/reset the timer and indirectly call a function to perform a task context switch. This is another example of code that is Board and Processor specific.

 

NOTE : You must ONLY enable timer interrupts in your application after the OS has been initialized, by a call to OSInit() and after you have created your application’s threads. At this point, your application’s main() can call OSStart() which starts the OS, then you should initialize timer interrupts within the first task that executes following a call to OSStart(). See the example application near the end of this assignment.

 

A common mistake when writing your application is to enable timer interrupts between calling OSInit() and OSStart(), as shown in Listing 13.14. This is a problem because the timer interrupt could be serviced before μC/OS-II starts the first task and, at that point, μC/OS-II is in an unknown state and your application could crash.

 

 

The pseudo code for the OSTickISR() function is shown below in Listing 13.15. This code must be written in assembly language because you cannot access CPU registers directly from C.

 

 

 

The implementation of the function OSTickISR() above is given below in 68k Assembly language

 

_OSTickISR:

        or.w      #$0700,SR                                      ; Disable ALL interrupts by masking 68k interrupts

        addq.b    #1,_OSIntNesting                       ; OSIntNesting++;

        movem.l   A0-A6/D0-D7,-(A7)                  ; Save the 68k registers of the current task

        

        ; call your ISR here to clear the tick interrupt

        jsr       _Timer_ISR                                        ; we have to write this Fn to reset the timer interrupt

                                                                                ; the code for this function is in the file BIOS.c

        jsr       _OSTimeTick                                     ; Call uC/OS-II's tick updating function

        bra       OSIntExit68K                                   ; branch (or jump) to OS function to Exit ISR

 

You will notice this Interrupt service routine also calls OSIntExit68k() - the 68k version of the OSIntExit() to perform the important task of performing a context/task swap (by calling OSIntCtxSw()) and then returning from the interrupt service routine. 

 

The pseudocode for the OSIntExit() is given below, taken from page 96 of “uCOS-II The Real Time Kernel” (not the user manual) on Canvas

 

 

Its implementation is given below

 

OSIntExit68K:

        subq.b    #1,_OSIntNesting                  ; if (--OSIntNesting == 0)

        bne       OSIntExit68K_1

        tst.b     _OSLockNesting                      ; if (OSLockNesting == 0)

        bne       OSIntExit68K_1

 

;       re-enabling interrupts

        move.w    (60,A7),D0                           ; must be LAST nested ISR

        and.w     #$0700,D0                             ; do we want to change S bit in SR

 

        bne       OSIntExit68K_1

        lea       _OSUnMapTbl,A0                     ;  y = OSUnMapTbl[OSRdyGrp];

        clr.l     D0

        move.b    _OSRdyGrp,D0

        move.b    0(A0,D0.L),D1                       ;  y in D1

;

        lea       _OSRdyTbl,A0                          ;  OSPrioHighRdy = (INT8U)((y << 3) +  OSUnMapTbl[OSRdyTbl[y]]);

        clr.l     D0

        move.b    D1,D0

        lea       0(A0,D0.L),A0

        clr.l     D0

        move.b    (A0),D0               ;  OSRdyTbl[y] in D0

        lea       _OSUnMapTbl,A0

        lea       0(A0,D0.L),A0         ;  &OSUnMapTbl[OSRdyTbl[y]] in A0

        move.b    D1,D0

        lsl.b     #3,D0                 ;  (y << 3) in D0

        add.b     (A0),D0

        move.b    D0,_OSPrioHighRdy

;

        cmp.b     _OSPrioCur,D0           ; if (OSPrioCur != OSPrioHighRdy) {

        beq.s     OSIntExit68K_1

;

        lea       _OSTCBPrioTbl,A0        ; OSTCBHighRdy  = OSTCBPrioTbl[OSPrioHighRdy];

        clr.l     D1

        move.b    D0,D1

        lsl.l     #2,D1

        lea       0(A0,D1.L),A0

        move.l    (A0),_OSTCBHighRdy

;

        addq.l    #1,_OSCtxSwCtr          ; OSCtxSwCtr++;

;

        move.l    _OSTCBCur,A0                         ; Save stack pointer in the suspended task TCB

        move.l    A7,(A0)

        jsr       _OSTaskSwHook           ;    Invoke user defined context switch hook

        move.l    _OSTCBHighRdy,A0        ;    OSTCBCur  = OSTCBHighRdy

        move.l    A0,_OSTCBCur

        move.b    _OSPrioHighRdy,_OSPrioCur     ;    OSPrioCur = OSPrioHighRdy

        move.l    (A0),A7               ;    Get the stack pointer of the task to resume

OSIntExit68K_1:

        movem.l   (A7)+,A0-A6/D0-D7               ;  Restore the CPU registers

        rte                                     ;  Return to task or nested ISR

 

The Context Switch Function OSIntCtxSw()

OSIntCtxSw() is called by OSIntExit() (see above) to perform a context switch from an ISR. Because OSIntCtxSw() is called from an ISR, it is assumed that all the processor registers are properly saved onto the interrupted tasks stack.

 

The pseudo code for OSIntCtxSw() is shown in Listing 13.16. This code must be written in assembly language because you cannot access CPU registers directly from C. 

 

 

 

;****************************************************************************************

;                                      INTERRUPT LEVEL CONTEXT SWITCH

;

; Description : This function is called from OSIntExit() in OS_CORE.C

;               Provided for backward compatibility.

;               The ISR MUST NOT call OSIntExit(), but should jump to OSIntExit68K().

;****************************************************************************************

 

_OSIntCtxSw:

        adda.l    #10,A7                         ; Adjust the stack (note this code is called as a subroutine by OS so extra copy of PC stored on stack - along with PC and SR - so adjust by 10 bytes to point to A6)

        move.l    _OSTCBCur,A1      ; Save the stack pointer in the suspended task TCB

        move.l    A7,(A1)

;

        jsr       _OSTaskSwHook     ; Invoke user defined context switch hook

;

        move.l    _OSTCBHighRdy,A1  ; OSTCBCur  = OSTCBHighRdy

        move.l    A1,_OSTCBCur

        move.l    (A1),A7           ; Get the stack pointer of the task to resume

;

        move.b    _OSPrioHighRdy,_OSPrioCur      ; OSPrioCur = OSPrioHighRdy

        movem.l   (A7)+,A0-A6/D0-D7              ; Restore the CPU registers

        rte                                      ; Run task

Making a Completely Rommable Application: The Boot Code

 

To make a completely rommable application, we have to create a 68k Boot assembly language file to start the system from a reset or power up, i.e. run our application main(), initialise stack pointer, install interrupt service routines for the timer that produces the interrupt for the real-time clock etc. 

 

Fortunately we already have some boot code to base it on, since it was used to create the boot code for the debug monitor (remember the CStart_Debug Monitor.asm Code that you used in Assignment 1). We can make our own version of that with 1 or two tweaks as outlined below

 

1)      Install the ISR for the Timer 1 interrupt “tick” connect to a Level 3 IRQ

2)      Install a TRAP #0 exception handler to handle an application/OS generated context switch.

 

The full OS_Boot_DE1.asm file is posted on Canvas but the critical bits of code to support points 1) and 2) above and other important intialisation are shown below in RED. You may want to revise Lecture 17 on Interrupts to understand this, in particular, Page 31, the Exception Vector Table

 

;********************************************************************************************************

;                                               uC/OS-II

;                                         The Real-Time Kernel

;

;                            (c) Copyright 1999, Jean J. Labrosse, Weston, FL

;                                          All Rights Reserved

;

;

;                                        IDE68K Specific boot code

;

;

; File         : OS_BOOT.ASM

; By           : PJ Davies to suite DE1 board

;********************************************************************************************************

 

;********************************************************************************************************

;                                               NOTES

;

; This is the "Board Support Package" or BSP for the DE1 board  It defines memory layout,

; interrupt vectors and a few BIOS functions.

;

;********************************************************************************************************

 

;********************************************************************************************************

;                                           REVISION HISTORY

;

; $Log$

;

;********************************************************************************************************

 

ROM        equ         $00000000       ; ROM starts at $00000000

RAM        equ         $08000000       ; RAM starts at $08000000

RAMsize    equ         $00010000       ; limit size of RAM to 64kbytes even though we have a ton of Dram

 

;           option      S0              ; Generate S0 record in .hex file since Rom is at location 0

 

           section     code                                             ; all multiple code sections merged here at link time

           org         ROM                                              ; starting at location 0, generate the following constants

begin_ROM  equ         *                                                ; beginning of rom constant is 0

code       equ         *                                                ; code starts at 0

 

           section     const                                            ; constants placed in this section at link time

const      equ         *                                                ; consts start whever the linker groups them, after the code section (but still in rom)

 

           section     data                                             ; all program variables etc (data) are grouped together here by the linker

end_ROM    equ         *                                                ; end of rom is wherever the last code/constant appears (* means here)

 

           org         RAM                                              ; starting at address $08000000

begin_RAM  equ         *                                                ; begin ram starts here

data       equ         *                                                ; data starts here also

 

           section     bss                                              ; this is the start of unintialised variable section. All C variables that are uninitialised get grouped together here and are set to zero at start of program

bss        equ         *

 

           section     heap

end_RAM    equ         *                                                ; wherever the program variables end, is the start of the heap

heap       equ         *

 

           section     code                                             

 

; going back to code section (still at location 0) reserve 256 long word vector table entries for the 68k (SEE TABLE PAGE 31/LECTURE 17)

 

;*******************************************************************************************************************

; start of 68000 vector table (256 long word entries covering reset, interrupts, initial stack pointer etc)

;*******************************************************************************************************************

 

InitialSP       dc.l __stack           ;initial supervisor stack pointer value (stack decrements first before being used)

InitialPC       dc.l startup           ;address of 1st instruction of program to run after a reset

BusError        dc.l E_BErro           ;bus error - stop program

AddressError    dc.l E_AErro           ;address error - stop program

IllegalInstr    dc.l E_IInst           ;illegal instruction - stop program

DividebyZero    dc.l E_DZero           ;divide by zero error - stop program

Check           dc.l E_Check           ;Check instruction - stop program

TrapV           dc.l E_Trapv           ;Trapv instruction - stop program

Privilege       dc.l E_Priv            ;privilige violation - stop program

Trace           dc.l E_Trace           ;stop on trace

Line1010emul    dc.l E_1010            ;1010 instructions stop

Line1111emul    dc.l E_1111            ;1111 instructions stop

Unassigned1     dc.l E_Unnas1          ;unassigned vector

Unassigned2     dc.l E_Unnas2          ;unassigned vector

Unassigned3     dc.l E_Unnas3          ;unassigned vector

Uninit_IRQ      dc.l E_UnitI           ;uninitialised interrupt

Unassigned4     dc.l E_Unnas4          ;unassigned vector

Unassigned5     dc.l E_Unnas5          ;unassigned vector

Unassigned6     dc.l E_Unnas6          ;unassigned vector

Unassigned7     dc.l E_Unnas7          ;unassigned vector

Unassigned8     dc.l E_Unnas8          ;unassigned vector

Unassigned9     dc.l E_Unnas9          ;unassigned vector

Unassigned10    dc.l E_Unnas10         ;unassigned vector

Unassigned11    dc.l E_Unnas11         ;unassigned vector

SpuriousIRQ     dc.l E_Spuri           ;stop on spurious irq

*

*

Level1IRQ       dc.l Level1RamISR

Level2IRQ       dc.l Level2RamISR

Level3IRQ       dc.l _OSTickISR        ; install a Level3RamISR (Timer Tick) - ISR for the DE1 timer 1 "tick" 

Level4IRQ       dc.l Level4RamISR

Level5IRQ       dc.l Level5RamISR

Level6IRQ       dc.l Level6RamISR

Level7IRQ       dc.l Level7RamISR

*

*

Trap0           dc.l _OSCtxSw           ; (Context Switch) - invoked by a trap 0 instruction code found in os_cpu.h file

Trap1           dc.l Trap1RamISR        ; User installed trap handler

Trap2           dc.l Trap2RamISR        ; User installed trap handler

Trap3           dc.l Trap3RamISR        ; User installed trap handler

Trap4           dc.l Trap4RamISR        ; User installed trap handler

Trap5           dc.l Trap5RamISR        ; User installed trap handler

Trap6           dc.l Trap6RamISR        ; User installed trap handler

Trap7           dc.l Trap7RamISR        ; User installed trap handler

Trap8           dc.l Trap8RamISR        ; User installed trap handler

Trap9           dc.l Trap9RamISR        ; User installed trap handler

Trap10          dc.l Trap10RamISR       ; User installed trap handler

Trap11          dc.l Trap11RamISR       ; User installed trap handler

Trap12          dc.l Trap12RamISR       ; User installed trap handler

Trap13          dc.l Trap13RamISR       ; User installed trap handler

Trap14          dc.l Trap14RamISR       ; User installed trap handler

Trap15          dc.l Trap15RamISR       ; User installed trap handler 

 

*

* Other vectors 64-255 are users vectors for autovectored IO device (not implemented in soft core 68k)

*

 

                org       $00000400    ; end of vector table/start of boot code

 

           ; this is where the program code initially begins (see table above vector #1 - initial Program counter value is defined as "startup"

           ; here we can write some boot code and carry out some memory/constant initialisation

           ; add your own code here or you can do it later in C (try to keep assembler code to a minimum)

startup:

           lea         bss,A0                               ; put start address of unitialised variables into register A0

           clr.b       (A0)+           ; set bss section (unitialised variables) to zero (clear the byte pointed to by A0 and then increment A0)

           cmp.l       #heap,A0                             ; compare A0 with immediate value defined by heap

           bcs.s       *-8                                              ; if not there yet go back 8 bytes to clr.b instruction

           move.l      #-1,__ungetbuf  ; initialose ungetbuffer for keyboard input (don't remove this otherwise scanf() etc will not work)

           ;

           move.l      #(end_ROM-begin_ROM),__romsize       ; initialise some values related to rom and ram limits (needed by OS)

           move.l      #(end_RAM-begin_RAM),__ramsize

           jsr         _main                                ; now call main() in our application (yeah!!!!)

 

 

The Board Support Files : Bios.c and Bios.h files

 

Also posted on Canvas are two files: Bios.c and Bios.h which contains some basic IO software ‘C’ code routines that will allow printf() to talk to the serial port connected to hyperterminal and some routines to drive the LCD display (not used on DE1SoC). These routines can be called from within your application and are intended to supply low level Basic IO functionality for our application. You can see the example application below, the function main() calls routines from Bios.c to initialise the LCD display and the RS232 Serial port.

 

You will also see that main() below calls the LCD function OLine0() and OLine1() to write messages to the LCD display. These functions can be found in Bios.c You can add you own as you deem appropriate.

 

An important pair of functions contained in Bios.c are the functions to initialise Timer 1 to produce the 100Hz interrupts used for timeslicing and the function to handle the interrupt from the timer. The code for these two functions is shown below. 

 

/*********************************************************************************

** Timer ISR

**********************************************************************************/

void Timer_ISR(void)

{

       if(Timer1Status == 1) {       // Did Timer 1 produce the Interrupt?

           Timer1Control = 3;        // if so clear interrupt and restart timer

       }

}

 

/**********************************************************************************

** Timer Initialisation Routine

**********************************************************************************/

void Timer1_Init(void)

{

    Timer1Data = 0x03;       // program 100 hz time delay into timer 1.                            

 

/*

** timer driven off 25Mhz clock so program value so that it counts down in 0.01 secs

** the example 0x03 above is loaded into top 8 bits of a 24 bit timer so reads as 

** 0x03FFFF a value of 0x03 would be 262,143/25,000,000, so is close to 1/100th sec

**

**

** Now write binary 00000011 to timer control register: 

**     Bit0 = 1 (enable interrupt from that timer) 

**     Bit 1 = 1 enable counting

*/

 

    Timer1Control = 3;       

}

 

The user application is responsible for initialising the timer (see example Micrium application below), while the ISR for the timer is called by code described earlier in this assignment.

 

Summary

 

We took the user manual for uC/OS-II and re-wrote the simple files required to support the OS on a particular board and processor (DE1/68k). The code shown above has all been written and is posted on Canvas. All you have to do is download the files and use them to replace the ones in your C:\IDE68k\uCOSII folder. You don’t actually have to write any of them yourself.

 

Assuming you’ve done that, we can now write a simple multi-thread application to run on our DE1. The source code for the application is given below. It consists of 4 prioritised tasks/thread. With this particular OS, the lower the numerical priority value, the higher the actual task priority. Notice also how the allocation of stack space for each task is actually the responsibility of the application itself. In this case, an array of 256 bytes of stack space is set aside for each thread – this could of course be changed.

 

One more important thing to notice, is that uC/OS II will always attempt to run the task with the highest priority (assuming it is not blocked – e.g. waiting on a semaphor). That is, if you have two running, non-blocked tasks, one high priority, one low priority, the low priority task will never get to run since the high priority tasks will always get all the CPU time. 

 

This means we should write our applications in such a way as not to “hog” all the CPU time. In effect, they should be written to “yield” the CPU periodically so that other low priority tasks can receive some CPU time. This the purpose of a function call such as  OSTimeDly(30) which is called within the tasks below. It yields or suspends the task for 30 ticks of the timer clock.

 

Example Mult-threaded application for Micrium uC/OS-II

 

/*

 * EXAMPLE_1.C

 *

 * This is a minimal program to verify multitasking.

 *

 */

 

#include <stdio.h>

#include <Bios.h>

#include <ucos_ii.h>

 

#define STACKSIZE  256

 

/* 

** Stacks for each task are allocated here in the application in this case = 256 bytes

** but you can change size if required

*/

 

OS_STK Task1Stk[STACKSIZE];

OS_STK Task2Stk[STACKSIZE];

OS_STK Task3Stk[STACKSIZE];

OS_STK Task4Stk[STACKSIZE];

 

 

/* Prototypes for our tasks/threads*/

void Task1(void *);   /* (void *) means the child task expects no data from parent*/

void Task2(void *);

void Task3(void *);

void Task4(void *);

 

/* 

** Our main application which has to

** 1) Initialise any peripherals on the board, e.g. RS232 for hyperterminal + LCD

** 2) Call OSInit() to initialise the OS

** 3) Create our application task/threads

** 4) Call OSStart()

*/

 

void main(void)

{

    // initialise board hardware by calling our routines from the BIOS.C source file

 

    Init_RS232();

    Init_LCD();

 

/* display welcome message on LCD display */

 

    Oline0("Altera DE1/68K");

    Oline1("Micrium uC/OS-II RTOS");

 

    OSInit();         // call to initialise the OS

 

/* 

** Now create the 4 child tasks and pass them no data.

** the smaller the numerical priority value, the higher the task priority 

*/

 

    OSTaskCreate(Task1, OS_NULL, &Task1Stk[STACKSIZE], 12);     

    OSTaskCreate(Task2, OS_NULL, &Task2Stk[STACKSIZE], 11);     // highest priority task

    OSTaskCreate(Task3, OS_NULL, &Task3Stk[STACKSIZE], 13);

    OSTaskCreate(Task4, OS_NULL, &Task4Stk[STACKSIZE], 14);     // lowest priority task

 

    OSStart();  // call to start the OS scheduler, (never returns from this function)

}

 

/*

** IMPORTANT : Timer 1 interrupts must be started by the highest priority task 

** that runs first which is Task2

*/

 

void Task1(void *pdata)

{

 

    for (;;) {

       printf("This is Task #1\n");

       OSTimeDly(30);

    }

}

 

/*

** Task 2 below was created with the highest priority so it must start timer1

** so that it produces interrupts for the 100hz context switches

*/

 

void Task2(void *pdata)

{

    // must start timer ticker here 

 

    Timer1_Init() ;      // this function is in BIOS.C and written by us to start timer      

 

    for (;;) {

       printf("....This is Task #2\n");

       OSTimeDly(10);

    }

}

 

void Task3(void *pdata)

{

    for (;;) {

       printf("........This is Task #3\n");

       OSTimeDly(40);

    }

}

 

void Task4(void *pdata)

{

 

    for (;;) {

       printf("............This is Task #4\n");

       OSTimeDly(50);

    }

}

 

 

 

Building a uC/OS-II Application and Project

 

To build an application that uses the Micrium OS, we have to write and add our own application code to an IDE68k project along with some of the source files from Micrium as shown below. The OS_Boot_DE1.asm boot code file has to be at the top of the list so it is linked in at address 0 on the 68k, this is because it contains the boot code and the vector table for the 68k (remember this needs to be at location 0). Some of these files will already be present in the IDE68k installation folder of your disk. The ones on Canvas should be used to replace/add to those. 

 

 

 

When you compile this project it will produce code (in the form of SRecords) to be loaded at address 0 where our on board ROM sits. You can use the Visual C++ project StoMifConverter.c program you used in earlier assignments to convert the SRecord files to Altera MIF files and use the “In content memory editor” to download the code to the Altera Board’s ROM1 (just like we did in assignment 1 for the debug monitor). When you do this, and preset reset the application should spring to life and start multi-tasking.

 


More products