|
|
Embedded Code |
High Level Code generation in C, C++, Visual C++ |
RTOS The complete program with two tasks for the HC12 oscilloscope image FirstStep: only the main SecondStep: add interrupt ThirdStep: stack operations The function taskDelay(oscilloscope image) The IDLE task (oscilloscope image The source code for 4 tasks See all the variables with LX12.exe 2) MyRTOS by Orgler for Arduino UNO 2.1) the source code feedback:orgler@tin.it |
This short program can run on every microcontroller. Only the STACK must be adapted to your controller. For this controller the stack address for TASK1 begins with 0x22FF and the stack address for TASK2 with 0x23FF We need less than 16 Bytes for every stack. to adapt this program to your own µC see the step by step prorgrams beginning with the FirstStep #pragma DATA_SEG VAR_PAG_0 unsigned long cAddrTask1; unsigned long cAddrTask2; unsigned char *pSPtask1; // 1 unsigned char *pSPtask2; // 2 unsigned char cTaskNr; void task1(void); void task2(void); #pragma CODE_SEG DEFAULT void main(void) { //----prepare timer interrupt------------- PITCE = 0x01; PITINTE = 0x01; PITLD0 = 200; PITCFLMT = 0xA0; //---- init port as output --------------- DDRP = 0xFF; PTP = 0; //---------------------------------------- cAddrTask1 = (unsigned long)&task1; cAddrTask2 = (unsigned long)&task2; //------- prepare task2------------------------------------ //task2(); pSPtask2 = (unsigned char*) 0x23FF; *pSPtask2-- = (cAddrTask2 >> 8) & 0xFF; *pSPtask2-- = (cAddrTask2 >> 16) & 0xFF; *pSPtask2-- = 0xFF; *pSPtask2-- = 0xEE; *pSPtask2-- = 0xDD; *pSPtask2-- = 0xCC; *pSPtask2-- = 0xAA; *pSPtask2-- = 0xBB; *pSPtask2-- = 0; // CCR with I Bit cleared *pSPtask2 = 0; // CCR //------------------------------------------- //------------- prepare task1 ------------------------------ //task1(); pSPtask1 = (unsigned char*) 0x22FF; *pSPtask1-- = (cAddrTask1 >> 8) & 0xFF; *pSPtask1-- = (cAddrTask1 >> 16) & 0xFF; *pSPtask1-- = 0xFF; *pSPtask1-- = 0xEE; *pSPtask1-- = 0xDD; *pSPtask1-- = 0xCC; *pSPtask1-- = 0xAA; *pSPtask1-- = 0xBB; *pSPtask1-- = 0; // CCR:H with I Bit cleared *pSPtask1 = 0; // CCR cTaskNr = 1; //------------------------------------------- asm { LDS pSPtask1 RTI } //------ for(;;) { }// loop } // -------------------------------------------------------------------------------------- // PIT0 Timer Interrupt Service Routine ///////////////////////////////////////////////////////////////////////////////////////// #pragma CODE_SEG __NEAR_SEG NON_BANKED interrupt void PIT0_ISR(void) // xx µsec { PTP ^= 0x40; //interrupt timing control PITTF = 0x01; if(cTaskNr == 1) { cTaskNr = 2; asm STS pSPtask1 // save from last task asm LDS pSPtask2 // load for the new task } else { cTaskNr = 1; asm STS pSPtask2 // save from last task asm LDS pSPtask1 // load for the new task } } //---------------- finished interrupt ---------- #pragma CODE_SEG DEFAULT void task1(void) { int xCount; xCount = 0; for(;;) { if(xCount++ >= 30000) { xCount = 0; PTP ^= BIT5; } }//==== loop task1 ======== } void task2(void) { int xCount; xCount = 0; for(;;) { xCount++; if(xCount == 5000) { xCount = 0; PTP ^= BIT1; } }//=== loop task 2 ============ } C1 yellow: interrupt timing or scheduler timing C4 green: task0 C2 magenta: task1 Each task is a program with an entry point and an infinite loop. The entry point is like the main if we have only one task. The variables declared after the entry point are only visible in the same task. They are located on the stack and in this task always valid. Other possible variables are global variables. The scheduler uses global variables, lists, queues and semaphores are global variables. You can make your own global variables to control data exchange from one task to another or from interrupt routines to a task. Example for a simple semaphore: Variable cByteControl1 equal NULL or equal TWO: task0 write datas and than sets cByteControl1=1; task1 can read if "cByteControl1==1" and after reading sets to zero or better to two. instead of port PTP you can use your preferred port #pragma CODE_SEG DEFAULT void main(void) { unsigned int xCount; //---- init port as output --------------- DDRP = 0xFF; PTP = 0; //---------------------------------------- xCount = 0; //---- main loop ----- for(;;) { if(xCount++ > 20000) { xCount = 0; PTP ^= 0x80; // toggle Bit7 form Port PTP } }// loop } you must add the interrupt vector to your vector table the output on our PORT PTP BIT6 must have a about 1/2 Vdd #pragma CODE_SEG DEFAULT void main(void) { unsigned int xCount; //----prepare timer interrupt------------- PITCE = 0x01; PITINTE = 0x01; PITLD0 = 200; PITCFLMT = 0xA0; //---- init port as output --------------- DDRP = 0xFF; PTP = 0; //---------------------------------------- EnableInterrupts; xCount = 0; //---- main loop ----- for(;;) { if(xCount++ > 20000) { xCount = 0; PTP ^= 0x80; // toggle Bit7 form Port PTP } }// loop } // -------------------------------------------------------------------------------------- // PIT0 Timer Interrupt Service Routine ///////////////////////////////////////////////////////////////////////////////////////// #pragma CODE_SEG __NEAR_SEG NON_BANKED interrupt void PIT0_ISR(void) // xx µsec { PTP ^= 0x40; //BIT 6 interrupt timing control PITTF = 0x01; // reset interrupt flag } //---------------- finished interrupt ---------- The address (in this example 0x22FF) must be located in our RAM-Space As a first step we save the address of the task function. Be aware of the memory model of your microcontroller. For the HCS12 a function address had 3 bytes. One Byte is for the global page. The byte for global page we need only for banked model. After the address we insert dummy values for the registers. The first time we jump in the task function this values can be any. //------------- prepare task1 ------------------------------ //task1(); pSPtask1 = (unsigned char*) 0x22FF; *pSPtask1-- = (cAddrTask1 >> 8) & 0xFF; *pSPtask1-- = (cAddrTask1 >> 16) & 0xFF; *pSPtask1-- = 0xFF; *pSPtask1-- = 0xEE; *pSPtask1-- = 0xDD; *pSPtask1-- = 0xCC; *pSPtask1-- = 0xAA; *pSPtask1-- = 0xBB; *pSPtask1-- = 0; // CCR:H with I Bit cleared *pSPtask1 = 0; // CCR cTaskNr = 1; //------------------------------------------- asm { LDS pSPtask1 RTI }Now we need same assembler instructions. We load the StackPointer (SP) with the content of pSPtask1 ( in this example 0x22F6) The RTI now is magic. This command (return from interrupt) does restore all registers with the values saved on the actual stack. Because CCR is set to zero, the I Bit is also cleared and the interrupts are enabled. We don't need any extra command like "EnableInterrupts" to start interrupts. The PC (ProgramCounter) the HCS12 has the IP (Instructions Pointer) is loaded with the start address of the task1 function, after the RTI command we continue with the task1 function. We never return in the main function. The task1 function has a loop forever and we remain inside this loop until the first interrupt is issued. We said that an interrupt first saves all registers on the stack, set the I-Bit in CCR to one and blocks all other possible interrupts. We don't have any. Now we change the Stack Pointer to the stack of the other task and the last command of an interrupt function is the RTI. This RTI is not visible in the program code, but the compiler inserts at the end of an interrupt function always an RTI. At the end of a normal function is we have always a RTS. interrupt void PIT0_ISR(void) // xx µsec { PTP ^= 0x40; //interrupt timing control PITTF = 0x01; if(cTaskNr == 1) { cTaskNr = 2; asm STS pSPtask1 // save from last task asm LDS pSPtask2 // load for the new task } else { cTaskNr = 1; asm STS pSPtask2 // save from last task asm LDS pSPtask1 // load for the new task } } the scheduler returns in the red task and continues the elaboaration. at the end of the loop there is a taskdelay(3); Thats means that we enter in the function of "taskdelay" but in this function there is a context switch to the next task( to the task green) every task returns at the end of the loop to the scheduler over the function taskDelay and will not called about the number of thicks indicated. The green task has taskDelay(2), the red task has taskDelay(3); The scheduler tries to start immediatly the next task. If there is not another task able to start, the scheduler starts the IDLE task (C3 blue) this source has less than 300 lines and is able to start at least 4 tasks. All the tasks has the same priority and this maybe right for many embedded applications This code can be easy modified for every microcontroller #pragma DATA_SEG VAR_PAG_0 unsigned char cTaskNr; unsigned char cxTaskBlocked[4]; unsigned char cxBITControl[4]; unsigned int uTaskThicks[4]; unsigned long cAddrIdle; unsigned long ulAddrTask[4]; // address of task function unsigned char *pSPtask[4]; // task stack pointer unsigned char cTaskMax; unsigned char cTaskCount; // use for scheduler unsigned char *pSPIdle; unsigned char *pSP; unsigned int uThickCounter; // interrupt counter #pragma CODE_SEG DEFAULT void task0(void); void task1(void); void taskDelay(unsigned int uThicks); void taskIdle(void); //--------- utility -------------- void prepareStack(unsigned int uAddrStack) { pSP = (unsigned char*) uAddrStack; *pSP-- = (ulAddrTask[cTaskMax] >> 8) & 0xFF; *pSP-- = (ulAddrTask[cTaskMax] >> 16) & 0xFF; *pSP-- = 0xFF; *pSP-- = 0xEE; *pSP-- = 0xDD; *pSP-- = 0xCC; *pSP-- = 0xAA; *pSP-- = 0xBB; *pSP-- = 0; // CCR:H with I Bit cleared *pSP = 0; // CCR pSPtask[cTaskMax] = pSP; } void main(void) { asm // clear RAM { ldx #0x2000; NX: CLR 0,X INX CPX #0x4000 BLO NX } Sci0_Init(); //--------------prepare timer interrupt------------- PITCE = 0x01; PITINTE = 0x01; PITLD0 = 2000; PITCFLMT = 0xA0; //---- init port as output ------------------------- DDRA = 0xFF; PORTA = 0; DDRP = 0xFF; PTP = 0; //-------------------------------------------------- //----------- prepare IDLE task ------------------ cAddrIdle = (unsigned long)&taskIdle; pSPIdle = (unsigned char*) 0x24FF; *pSPIdle-- = (cAddrIdle >> 8) & 0xFF; *pSPIdle-- = (cAddrIdle >> 16) & 0xFF; *pSPIdle-- = 0xFF; *pSPIdle-- = 0xEE; *pSPIdle-- = 0xDD; *pSPIdle-- = 0xCC; *pSPIdle-- = 0xAA; *pSPIdle-- = 0xBB; *pSPIdle-- = 0; // CCR with I Bit cleared *pSPIdle = 0; // CCR //------------------------------------------------ //------------- prepare task0 ---------------------- cTaskMax = 0; ulAddrTask[cTaskMax] = (unsigned long)&task0; prepareStack(0x22FF); cxBITControl[cTaskMax]=BIT0; cTaskMax++; //------- prepare task1---------------------------- ulAddrTask[cTaskMax] = (unsigned long)&task1; prepareStack(0x23FF); cxBITControl[cTaskMax]=BIT1; cTaskMax++; //-------------------------------------------------- //--- we start a task ------------------------------ cTaskNr = 0; PORTA |= cxBITControl[cTaskNr]; pSP = pSPtask[cTaskNr]; asm LDS pSP // load for the new task asm RTI //-- the RTI restores the context of task0 and enables the interrupts //-- the programm continues now with the entry point of task0 //--- loop forever the program never riches this lines-------- for(;;) { }// loop } // -------------------------------------------------------------------------------------- // PIT0 Timer Interrupt Service Routine ///////////////////////////////////////////////////////////////////////////////////////// #pragma CODE_SEG __NEAR_SEG NON_BANKED interrupt void PIT0_ISR(void) // xx µsec { asm STS pSP // save from last task PORTA = 0; PTP |= BIT6; //BIT 6 interrupt timing control PITTF = 1; //reset interrupt flag uThickCounter++; if(uThickCounter == uTaskThicks[0]) cxTaskBlocked[0] = 0; if(uThickCounter == uTaskThicks[1]) cxTaskBlocked[1] = 0; if(uThickCounter == uTaskThicks[2]) cxTaskBlocked[2] = 0; if(uThickCounter == uTaskThicks[3]) cxTaskBlocked[3] = 0; if(cTaskNr & BIT7) pSPIdle= pSP; // save from last task else pSPtask[cTaskNr] = pSP; cTaskNr &= 0x7F; // last tasknr //=================== scheduler1 ================================ cTaskCount=0; while(cTaskCount < cTaskMax) { cTaskCount++; cTaskNr++; if(cTaskNr >= cTaskMax) cTaskNr=0; if(cxTaskBlocked[cTaskNr]==0) // check if blocked { PORTA |= cxBITControl[cTaskNr]; pSP = pSPtask[cTaskNr]; asm LDS pSP // load for the new task PTP &= BIT6_INVERS; return; } } PORTA |= BIT7; cTaskNr |= BIT7; asm LDS pSPIdle // load for the new task PTP &= BIT6_INVERS; //==================== end of scheduler1 ========================== } //---------------- finished interrupt --------------------- #pragma CODE_SEG DEFAULT void task0(void) { int xCount; xCount = 0; for(;;) { if(xCount++ >= 1000) { xCount = 0; PTP ^= BIT5; } taskDelay(3); // delay for 3 thicks }//========== loop task0 ============= } void task1(void) { int xCount; xCount = 0; for(;;) { xCount++; if(xCount == 500) { xCount = 0; PTP ^= BIT2; } taskDelay(2); }//=========== loop task 2 ============ } //============this functions don't return =================== void taskDelay(unsigned int uThicks) { DisableInterrupts; uTaskThicks[cTaskNr] = uThickCounter + uThicks; asm TSX asm INX asm INX asm CLRA asm STAA 0,X asm DEX asm STAA 0,X asm DEX asm STAA 0,X asm DEX asm STAA 0,X asm DEX asm STAA 0,X asm DEX asm STAA 0,X asm DEX asm STAA 0,X asm DEX asm STAA 0,X asm STX pSP pSPtask[cTaskNr] = pSP; cxTaskBlocked[cTaskNr]= 1; //=== the same scheduler as in the interrupt function==== PORTA = 0; cTaskCount=0; while(cTaskCount < cTaskMax) { cTaskCount++; cTaskNr++; if(cTaskNr >= cTaskMax) cTaskNr=0; if(cxTaskBlocked[cTaskNr]==0) // check if blocked { PORTA |= cxBITControl[cTaskNr]; pSP = pSPtask[cTaskNr]; asm LDS pSP // load for the new task asm RTI } } PORTA |= BIT7; cTaskNr |= BIT7; asm LDS pSPIdle asm RTI //============== end of scheduler2 ========================= } void taskIdle(void) { for(;;) { PTP ^= BIT0; } } A simple RTOS with two tasks for the Atmega328P compiled with Atmel Studio 6 and loaded to an Arduino UNO board. :100000000C9434000C9451000C9451000C94510049 :100010000C9451000C9451000C9451000C9451001C :100020000C9451000C9451000C9451000C9451000C :100030000C9451000C9451000C9451000C945100FC :100040000C947E000C9451000C9451000C945100BF :100050000C9451000C9451000C9451000C945100DC :100060000C9451000C94510011241FBECFEFD8E026 :10007000DEBFCDBF11E0A0E0B1E0E8E5F2E002C0F4 :1000800005900D92A030B107D9F711E0A0E0B1E0E2 :1000900001C01D92A131B107E1F70E94F0000C945C :1000A0002A010C94000040ED5DED66E070E020E276 :1000B000DB01CA010197A109B109E1F780910101B2 :1000C000909102010196909302018093010185B104 :1000D000822785B9EDCF28EE3DEF40E1C9010197B8 :1000E000F1F7809106019091070101969093070125 :1000F0008093060185B1842785B9F0CF1F920F92B6 :100100000FB60F9211248F939F932F923F924F928D :100110005F926F927F928F929F92AF92BF92CF9297 :10012000DF92EF92FF920F931F932F933F934F9382 :100130005F936F937F93AF93BF93CF93DF93EF93CF :10014000FF93809104018823A9F48DB78093050162 :100150008EB780930A01809103018DBF80910001C9 :100160008EBF95B182E0892785B9289881E0809378 :10017000040113C08DB7809303018EB780930001F3 :10018000809105018DBF80910A018EBF95B181E0FC :10019000892785B9299810920401FF91EF91DF9189 :1001A000CF91BF91AF917F916F915F914F913F91AF :1001B0002F911F910F91FF90EF90DF90CF90BF9004 :1001C000AF909F908F907F906F905F904F903F90F7 :1001D0002F909F918F910F900FBE0F901F901895A9 :1001E0008DE091E020E18030920730F4FC01119223 :1001F00080E1E030F807D9F78FEF84B981E0809390 :100200006E0023E025BD8BE690E090931001809373 :100210000F018093FF04809110018093FE048CED08 :100220008093030184E08093000183E590E0909344 :10023000100180930F018093FF0380911001809340 :10024000FE038DEF8093050120930A018DBF2EBF21 :080250001895FFCFF894FFCFD1 :00000001FF to load this hex data in the Arduino UNO copy the hex data in a file with the name MyRtosAVR.hex in the same directory open a black cmd window write this two lines in one line: avrdude -C avrdude.conf -q -q -patmega328p -carduino -P\\.\COM17 -b115200 -D -U flash:w:MyRtosAVR.hex:i you can copy this also in a batch file like xx.bat you must adapt only the COM port, instead COM17 you must insert your port. please note: you must have in the same directory this files: avrdude.exe avrdude.conf libusb0.dll MyRtosAVR.hex avrdude.exe, avrdude.conf and libusb0.dll you can found in the arduino directories. the yellow channel is from PORTB BIT4 and the two others are the alterantive running tasks /* * MyRtosAVR.c * * Created: 26.11.2012 * Author: Orgler Ludwig */ #define BIT0 0x01 #define BIT1 0x02 #define BIT2 0x04 #define BIT3 0x08 #define BIT4 0x10 #define BIT5 0x20 #define BIT6 0x40 #define BIT7 0x80 #include |