Implementation of porting μC/OS-II to MC68K using MC68K's C compiler

Publisher:SparklingBeautyLatest update time:2018-02-07 Source: eefocusKeywords:MC68K  μC  OS-II Reading articles on mobile phones Scan QR code
Read articles on your mobile phone anytime, anywhere

    1. Introduction to MC68K CPU

    The MC683xx series of microcontrollers are composed of the famous MOTOROLA 32-bit microprocessors such as MC68K, 68020, 68040, and the compatible 68K, CPU32, CPU32+ and other CPU expansion timing processing units TPU, queue serial modules QSM, system control modules and RAM.

    CPU32 has 8 32-bit general data registers and 8 32-bit general address registers. The 8 general data registers can be used as accumulators or as various types of variables in C language; the 8 general address registers can be used as index registers or as pointer variables in C language. CPU32 has independent user stack pointer and system stack pointer, which can distinguish storage spaces such as program area, data area, system area, user area, etc., and has 7 levels of interrupts.


    To realize the migration of μC/OS-II to MC68K, a C compiler for MC68K is required. We use the C compiler of HIWARE. This C compiler allows embedded line assembly.

    2. Files that need to be modified during migration

    There are three main files related to the CPU: C language file OS_CPU32.C, header file OS_CPU32.H and assembly file OS_CPU32.ASM.

    1.INCLUDES.H file

    INCLUDES.H is the main header file. All files with the .C suffix include the INCLUDES.H file at the beginning. For different types of processors, users need to modify the INCLUDES.H file and add their own header files, but they must be added at the end of the file. When installing μC/OS-II, several porting examples are included. For example, the code for Intel 80x86 is installed in the IIL directory. The porting examples we wrote for MC68K are all placed under II. In the INCLUDES.H file, add:

    #include "iiK_CPU32.ASM"

    #include "iiK_CPU32.C"

    #include "iiK_CPU32.H"

    2. OS_CPU32.H file

    The basic information related to the hardware is defined in the OS_CPU32.H file:

    typedef unsigned char INT8U; /*unsigned 8-bit number*/

    typedef signed char INT8S; /*Signed 8-bit number*/

    typedef unsigned int INT16U; /*unsigned 16-bit number*/

    typedef signed int INT16S; /*Signed 16-bit number*/

    typedef signed long INT32S; /*Signed 32-bit number*/

    typedef unsigned int OS_STK; /*Stack entry width is 16 bits*/

    #define OS_STK_GROWTH1 /*The stack grows from high address to low address*/

    #define UCOS 0 /*Soft interrupt for task switching*/

    define OS_TASK_SW() _TRAP(UCOS)

    #define OS_ENTER_CRIT IC AL() move.w#$2700,SR /*Enter critical section*/

    #define OS_EXIT_CRITICAL() move.w #$2000,SR /*Exit critical section*/

    (1) Data type

    Since different processors have different word lengths, the porting of μC/OS-II requires redefining a series of data structures. Since MC68K is a 32-bit MCU, integer (int) type data is 16 bits, and long integer (long) is 32 bits. In MC68K, stacks are operated by word, so the stack data type OS_STK is declared as 16 bits. All stacks must be declared with OS_STK.

    (2) Code Critical Section

    μC/OS-II turns off interrupts before entering the critical code area of ​​the system, and turns them on again after exiting the critical area, thereby protecting the core data from being destroyed by other tasks or interrupts in a multi-tasking environment. In MC68K, the interrupt switch can be achieved by setting the interrupt mask bit in the status register SR. The macro OS_ENTER_CRITICAL() in μC/OS-II defines setting the interrupt mask bit in the status register to mask all seven-level interrupts; OS_EXIT_CRITICAL() defines clearing the interrupt mask bit in the status register to turn on all seven-level interrupts. This processing method is very simple, but the hierarchical interrupt mechanism provided by CPU32 cannot be used. If hierarchical interrupts are to be used, some related functions must be rewritten, which will be explained in Section 4.

    (3) Stack direction

    The stack of the MC68K processor decreases from high address to low address, so OS_STK_GROWTH must be set to 1.

    (4) Definition of OS_TASK_SW() function

    In μC/OS-II, OS_TASK_SW() is used to implement task switching. The stack initialization of the ready task should simulate the appearance after an interrupt occurs, and the registers in the stack should be set in the order of stacking. The OS_TASK_SW() function simulates an interrupt process and performs task switching when the interrupt returns. CPU32 has 16 soft interrupts to choose from, called trap calls. The entry point of the interrupt program must point to the assembly function OSCtxSw().

    The trap call No. 0 that we use in the routines provided by μC/OS-II is defined by the following statement:

    #define OS_TASK_SW() -TRAP(UCOS)

    3. OS_CPU32.ASM file

    The porting of μC/OS-II requires the user to rewrite four functions in OS_CPU_A.ASM: OSStartHighRdy(), OSCtxSw(), OSI NTC txSw() and OST IC kISR().

    (1) OSStartHighRdy() function

    This function is called by the OSStart() function and its function is to run the highest priority ready task. Before calling OSStart(), the user must first call OSInit() and have created at least one task. To start the task, OSStartHighRdy() first finds the currently ready task with the highest priority. The address of the task control block (TCB) of the highest priority task is stored in OSTCBHighRdy, and the pointer to the stack is found from the task control block of the task. Then, the instruction MOVEM.L(A7)+, A0-A6/D0-D7 is executed to pop the contents of all registers from the stack and run the RTE interrupt return. Since the stack structure is initialized according to the interrupt capture stack structure when the task is created, the new task is switched after the RET instruction is executed. For the task switching mechanism of μC/OS-II, please refer to the series meter socket (3).

    The assembly code of OSStartHighRdy is as follows:

    _OSStarHighRdy

    MOVE.L(_OSTCBHighRdy), A1

    ;Get the TCB address of the highest priority ready task

    MOVE.L A1, (_OSTCBCur)

    MOVE.L (A1), A7; Get the stack pointer

    MOVEM.L (A7)+, A0-A6/D0-D7

    RTE; interrupt return, switch task

    (2) OSCtxSw() function

    OSCtxSw() is a task-level task switching function (called in the task, different from OSIntCtxSw() called in the interrupt program. On the MC68K system, task switching is implemented by executing a soft interrupt instruction. The soft interrupt vector points to the function, and the execution structure of the function may cause the system task to be rescheduled (for example, trying to wake up a higher priority task). At the end of the function, OSSched() will be called. OSSched() will find the currently ready task with the highest priority. If it is not the current task, determine whether task scheduling is required, find the address of the task control block OS_TCB, and copy the address to the variable OSTCBHighRdy, and then execute the soft interrupt through the pet OS_TASK_SW() to switch the task. In this process, the variable OSTCBCur always contains a pointer to the currently running task OS_TCB. The assembly code of OSCtxSw() is as follows:

    _OSCtxSw

    MOVEM.L A0-A6/D0-D7,-(A7); store the current task environment

    MOVE.L (_OSTCBCur), A1; Save the current task TCB pointer

    MOVE.L A7, (A1)

    MOVE.L (_OSTCBHighRdy), A1; Get the TCB address of the highest priority ready task

    MOVE.L A1, (_OSTCBCur); Set the ready task as the current running task

    MOVE.L (A1), A7; Get the stack pointer of the new task

    MOVEM.L (A7)+, A0-A6/D0-D7;

    RTE; interrupt return, switch task

    (3) OSIntCtxSw() function

    In μC/OS-II, since the generation of an interrupt may cause a task switch, the OSICntExit() function will be called at the end of the interrupt service program to check the task readiness status. If a task switch is required, OSIntCtxSw() will be called, so OSIntCtxSw() is also called the interrupt-level task switching function. Since an interrupt has occurred before OSIntCtxSw() is called, OSIntCtxSw() assumes that the CPU registers have been saved in the stack of the interrupted task. The code of OSIntCtxSw() is mostly the same as OSCtxSw(), except that: first, since the interrupt has already occurred, there is no need to save the CPU registers here; second, OSIntCtxSw() needs to adjust the stack pointer and remove some unnecessary content in the stack so that the stack contains the task's operating environment.

    _OSIntCtxSw

    ADDA #10, A7; Ignore the nested function call

    ; Use the contents pushed into the stack

    MOVE.L (_CSTCBCur), A1; Save the current

    ; Task stack pointer

    MOVE.L A7, (A1)

    MOVE.L (_OSTCBHighRdy), A1

    ;Get the TCB address of the highest priority ready task

    MOVE.L A1, (_OSTCBCur); Set the ready task device to the current

    ; Run the task

    MOVE.L (A1), A7; Get the stack pointer

    MOVEM.L (A7)+, A0-A6/D0-D7;

    RTE; interrupt return, switch task

    (4)OSTickISR() function

    In μC/OS-II, after calling OSStart() to start the multitasking environment, the clock interrupt is very important. All timing-related tasks are processed in the clock interrupt, such as task delay, waiting operation, etc. In the clock interrupt, the task in the waiting state will be queried to determine whether the delay is over, so as to reschedule the task.

   56

    Like other interrupt service routines in μC/OS-II, OSTICkISR ( ) first saves the value of the CPU register in the interrupted task stack, and then calls OSIntEnter(). ΜC/OS-II requires that OSIntEnter() be called at the beginning of the interrupt service routine, and its function is to add 1 to the global variable OSIntNesting that records the number of interrupt nesting levels. If OSIntEnter() is not called, it is also allowed to directly add 1 to OSIntNesting. Subsequently, OSTickISR() calls OSTimeTick() to check all tasks in the delayed waiting state and determine whether there is a task that has ended the delay and is ready. At the end of OSTickISR(), OSIntExit() is called. If there is a higher priority task ready in the interrupt (or other nested interrupts), and the current interrupt is the last layer of interrupt nesting, OSIntExit() will perform task scheduling. Note that if task scheduling is performed, OSIntExit() will no longer return to the caller, but will use the register values ​​in the new task stack to restore the CPU scene, and then use RTE to implement task switching. If the current interrupt is not the last layer of interrupt nesting, or the interrupt does not change the ready state of the task, OSIntExit() will return to the caller OSTickISR(), and finally OSTickISR() returns the interrupted task.

    4. OS_CPU32.C file

    The migration of μC/OS-II requires the user to define 6 functions in OS_CPU32.C, but in fact only OSTaskStkInit() needs to be defined. The other 5 functions need to be declared, but they may not have actual content. These 5 functions are all user-defined, so there is no code in OS_CPU32.C. If the user needs to use these functions, please set the #define constant OS_CPU_HOOKS_EN in the file OS_CDG.H to 1, and set it to 0 to not use these functions.

    The OSTaskStkInit() function is called by the task creation function OSTaskCreate() or OSTaskCreateExt() to initialize the task stack. The stack in the initial state simulates the stack structure after an interrupt occurs. The storage space of each register is reserved according to the stack push order after the interrupt, and the interrupt return address points to the starting address of the task code. When calling OSTaskCreate() or OSTaskCreateExt() to create a new task, the parameters that need to be passed are: the starting address of the task code, the parameter pointer, the address of the top of the task stack, and the priority of the task. OSTaskCreateExt() also requires some other parameters, but they are not related to OSTaskStkInit(). OSTaskStkInit() only needs the three parameters mentioned above: task, pdata, and ptos. Since the MC68K stack is 16 bits wide (in words), OSTaskStkInit() will create a pointer to a memory area in words, and requires the stack pointer to point to the top of the empty stack. After the stack initialization is completed, OSTaskStkInit() returns the new stack top pointer, and OSTaskCreate() or OSTaskCreateExt() saves the pointer in the task's OS_TCB.

    3. Some points to note during transplantation

    Due to the real-time nature of μC/OS-II, it is almost impossible to debug the kernel. Once the kernel runs unstably during the porting process, it is difficult to determine where the problem is. What is even more difficult is that some phenomena are almost impossible to repeat. This requires a detailed understanding of the kernel's operating mechanism and careful analysis to find out possible problems. Let's analyze these problems in the porting process.

    1. Compiler optimization options

    In the process of porting, in addition to being familiar with μC/OS-II and the target chip, it is also very important to be familiar with the C coder used . Usually the C compiler provides some options for optimizing the code, which often cause trouble in the process of porting μC/OS-II. The following is an example related to the HIWARE C compiler in the porting process.

    Usually when calling a subroutine or entering an interrupt, the C compiler will automatically save the CPU internal registers to the stack. For example, when entering an interrupt, the compiler will add the following two instructions:

    LINK #$0000,A6;

    MOVEM.L D0/D1/D3/D4/D5/D6/D7/A0/A1/A2/A3/A4/A5, -(A7);

    The purpose of these two assembly instructions is to save the CPU's data registers D0~D7 and address registers A0~A5 to the stack, and then save the stack pointer A7 to the stack, and use A6 as a temporary stack pointer. This is a very good optimization option to prevent accidental changes to data registers or address registers in interrupts; but in μC/OS-II, this mechanism will have a fatal impact on several subroutines and interrupt service routines in OS_CPU_C.C and OS_CPU_ASM.ASM.

    The subroutine interrupts in OS_CPU_C.C and OS_CPU_ASM.ASM trigger task scheduling, and the current task is suspended. Suspending the task is accomplished by the following statement:

    MOVEM.L A0-A6/D0-D7,-(A7);

    MOVE.L @OSTCBCur, A2;

    MOVE.L (A2), A1;

    MOVE.L A7,(A1);

    Save the task pointer and the values ​​of all data address registers. Ideally, the task stack at this time should be as shown in Figure 1 (taking the OSCtxSw() function as an example, it can correspond to other functions and interrupt handling routines in OS_CPU_C.C and OS_CPU_ASM.ASM).

    Then to resume the suspended task, just use the following statement:

    MOVE.L OSTCBHighRdy,A1;

    MOVE.L @OSTCBCur, A2;

    MOVE.L A1,(A2);

    MOVE.L (A1), A7;

    MOVEM.L (A7)+, A0-A6/D0-D7;

    Restore the task stack pointer saved in the task TCB, then restore the data address register, and finally execute the interrupt return of OSCtxSw() to successfully restore the suspended task.

    If the C compiler inserts two statements to save the data address register and the stack pointer at the entrance of the OSCtxSw() function and then executes the statement to suspend the task, the task stack will become as shown in Figure 2. The compiler causes the stack to change, and if all tasks are suspended and resumed in this way, there will be no fatal problems, because the encoder will insert the following statement to restore the stack when exiting the OSCtxSw() function:

    MOVEM.L (A7)+, D0-D7/A0-A5;

    UNLK A6;

    The problem is that when initializing tasks, each task is actually initialized according to the stack structure shown in Figure 1. Therefore, restoring according to the stack structure of Figure 2 will naturally cause the stack to crash.

    There are many ways to solve this problem. You can modify the task initialization code to adapt to this "optimization" of the C compiler, or you can first call the following statement to restore the stack when entering the OSCtxSw() function to offset the influence of the C encoder:

    MOVEM.L (A7)+, D0-D7/A0-A5;

    UNLK A6;

    Before exiting the OSCtxSw() function, call the following statement to simulate a changed stack:

    LINK #$0000,A6

    MOVEM.L D0-D7/A0-A5,-(A7);

    The better way is of course to adjust the compiler and disable this optimization option. If you cannot adjust the compiler, you can only use the above method to adapt to the compiler.

    2. Switch interruption method

    In μC/OS-II, the on/off interrupt is very important, which can ensure that the critical code or access to global variables is not accidentally affected by the interrupt. The interrupt control of CPU32 is relatively complex, providing 7 levels of interrupts with different levels; you can choose to turn off or on certain levels of interrupts. However, multi-level interrupts will make the interrupt handling of μC/OS-II complicated. In simple applications or when trying to port μC/OS-II for the first time, you can use the full on/off method.

 

    If you consider multi-level interrupts, you must note that the control of the interrupt switch level is an important information. This information needs to be saved before turning off the interrupt, and restored when turning on the interrupt. The easiest way to think of is to use a global variable to store this information.

    The procedure for using this method is as follows:

    #define OS_EXIT_CRIT IC AL() asm move SR_TEMP,sr;

    #define OS_ENTER_CRITICAL() asm move.w SR,SR_TEMP;

    asm ori.w #0x0700,SR;

    Then construct two tasks, each of which outputs a sentence to the screen. At the same time, modify the kernel code so that the idle task also outputs a sentence. When running the kernel, you will usually find that the kernel stops debugging within a few minutes, and only the idle task keeps outputting to the screen. This situation is very troublesome because it is impossible to determine when and where the kernel stops scheduling through debugging.

    Let's analyze it. When only the idle task is running, the code is:

    move.w sr,sr_temp

    ori.w #0700,sr

    addi.1 #1,OSIdle CTR

    move.w sr_temp,sr

    jmp ****

    These five statements are running in a loop, and interrupts (only timed interrupts at this time) can be cut in the middle of any statement. So, if an interrupt occurs during MOVE.W SR, SR_TEMP, the interrupt will be executed (because the interrupt is about to be turned off, but has not been turned off yet); and the OSIntENTER and OSIntEXIT called by the interrupt program will call OS_ENTER_CRITICAL() to turn off the interrupt and increment the global variable of the number of nested interrupts. At this time, when MOVE.W SR is executed again, the SR_TEMP variable is rewritten to the value of turning off the interrupt. When returning from the interrupt to the IDLE task to execute MOVE.W SR_TEMP, SR, the interrupt is turned off instead of restoring the original status register. This causes the kernel to be unable to respond to interrupts and unable to schedule tasks, and only the IDLE task is running.

    How to solve it? The easiest way to think of is to add another global variable to save the interrupt switch information when entering the interrupt, and restore this information when exiting the interrupt; but if interrupt nesting is taken into account, the same situation will occur again, and if a task is interrupted when executing MOVE.W SR, SR_TEMP and task scheduling occurs, then when the task is restored, the interrupt information SR_TEMP it uses may have been changed by other tasks. The kernel cannot respond to interrupts, and unschedulable tasks may still exist.

    Defining such a global variable for each task and interrupt seems to solve the problem without considering interrupt nesting, but imagine the workload of providing a separate OS_ENTER_CRITICAL() and OS_EXIT_CRITICAL() function for each task and interrupt. Obviously this is not a good solution.

    Pushing interrupt information onto the stack is a good idea, but we will see some more subtle and complex problems that come with it. The program code to implement this method is as follows:

    #define OS_ENTER_CRITICAL() asm move SR,-(A7);

    asm ori.w #0x0700,SR;

    #define OS_EXIT_CRITICAL() asm move (A7)+,sr;

    In this way, each time OS_ENTER_CRITICAL() is called, the current interrupt switch information is saved to the current task stack or system stack interrupt OS_EXIT_CRITICAL(), and this information is restored.

    After using this method, you must carefully calculate the stack usage and modify the functions in the OS_CPU_A.ASM and OS_CPU_C.C files. Take the OSI NTC txSw() function as an example. This function will cause interrupt-level task scheduling, that is, the program interrupted by the interrupt cannot continue to run, and another task with a higher priority in the exit interrupt can run. In this function, the interrupted task stack must be cleaned up so that the stack of this task looks the same as after a normal task switch, so as to ensure that the task is correctly resumed. The OSIntCtxSw() function is only called in the OSICntExit() function.

    It should be noted that when an interrupt occurs, CPU 32 has saved all registers, status registers, and PC pointer contents into the stack, thus preparing for the resumption of the interrupted task. If the normal interrupt process is followed, the interrupted task should resume running when the interrupt is exited. Now, due to the execution of the interrupt-level task switch, the interrupted task cannot be resumed immediately, but is suspended. This requires adjusting the stack before executing task scheduling, so that the interrupted task is in a state that can be resumed at any time.

    In the interrupt handler, when executing to OSIntExit(), the stack situation is the same as when the interrupt just occurred, and the interrupted task can be restored at any time. Therefore, you only need to ignore the stack changes caused by the OSIntExit() function. First, there is the return address of the OSIntExit() function itself, which is 2 words long; the status register pushed into the stack by calling OS_ENTER_CRITICAL() is 1 word long; finally, there is the return address of the OSIntCtxSw() function, which is 2 words long. So when OSIntCtxSw() performs task switching, the contents of the 5-word stack must first be cleared to ensure the correct recovery of the interrupted task. The statement is as follows:

    ADDA #10, A7;

    After completing these adjustments, the possibility of kernel scheduling deadlock caused by switch interrupts no longer exists. However, in this case, another more subtle problem will appear, which is related to the C coder used.

    The problem occurs when using the OSSemPend() function. Once this function is called, the CPU will have an address error and enter exception processing, and the kernel will be terminated. This problem is quite strange, because the OSSemPend() function is completely a sub-function written in C language, and the function itself should not have an address error. The problem was discovered by reading the target code compiled by the compiler. EmPend() function, it was found that this function does not have any local variables. When entering the OSSemPend() function, the compiler does not need to generate a LINK instruction to provide local variable space. All parameters are directly obtained from the stack using the address register indirect addressing method with an offset, and the address register used is the A7 register. The problem may be here. OS_ENTER_CRITICAL() and OS_EXIT_CRITICAL() will adjust the A7 register when operating on the stack, which will cause the following statements to be confused when using A7 as register indirect addressing, resulting in an address error.

    This requires a detailed study of the compiler's characteristics. The HIWARE compiler we use has actually taken this into account. When the OS_ENTER_CRITICAL() or OS_EXIT_CRITICAL() function is called and the A7 register is added, the indirect addressing using the A7 address register will also be adjusted accordingly to ensure that the variable passed when the function is called can still be obtained. For each OS_ENTER_CRITICAL(), the offset of the next A7 register indirect addressing will be increased by 2; for each OS_EXIT_CRITICAL(), the offset of the next A7 register indirect addressing will be reduced by 2. However, the problem still exists. The call to OSSemPend() will cause an address error, which should be a deeper error.

    The solution to this problem is to define a local variable, force the compiler to generate a LINK instruction, and construct an internal parameter addressing pointer A6. In this way, when OS_ENTER_CRITICAL() or OS_EXIT_CRITICAL() is called, only A7 is changed, and A6 is used for parameter addressing and is not affected.

    This problem can also be solved by forcing the compiler to add LINK and UNLINK instructions when calling functions, but the compiler optimization option problem mentioned first will be faced. It can be seen that the characteristics of the compiler are very important for porting μC/OS-II, and these characteristics often restrict each other.

    In the process of porting and running μC/OS-II, new problems may arise. When problems arise, as long as you carefully analyze the use of the stack, the impact of interrupts, and the compiled code, you can achieve stable and reliable operation of μC/OS-II.


Keywords:MC68K  μC  OS-II Reference address:Implementation of porting μC/OS-II to MC68K using MC68K's C compiler

Previous article:Design of remote network temperature control system based on embedded technology
Next article:General-purpose MPU based on MIPS processor core

Latest Microcontroller Articles
Change More Related Popular Components

EEWorld
subscription
account

EEWorld
service
account

Automotive
development
circle

About Us Customer Service Contact Information Datasheet Sitemap LatestNews


Room 1530, 15th Floor, Building B, No.18 Zhongguancun Street, Haidian District, Beijing, Postal Code: 100190 China Telephone: 008610 8235 0740

Copyright © 2005-2024 EEWORLD.com.cn, Inc. All rights reserved 京ICP证060456号 京ICP备10001474号-1 电信业务审批[2006]字第258号函 京公网安备 11010802033920号