uint8_t TTransceiver::Run (void) { // Run Statemachine, call this repeatedly.
Status |= TRANSCEIVER_STATUS_RUN;
if (TimeTicks) {
TimeTicks--;
}
while (Status & TRANSCEIVER_STATUS_RUN) {
(this->*pSMState)( ); // Execute the current/new state.
}
return Status;
}
(i saw some time ago some complex macro abortion that provided some sort of context switching somehow)
When i hear about things like an RTOS I always wonder how the microcontroller can actually switch from one function to another mid function and generally think that this should not be necessary if any one function is small enough that completing it's execution is not a problem in terms of time. Real urgent things are on interrupts anyway..
Using mutexes can become difficult to control and to prove their will never be deadlock or lovelock.
Putting messages in queues makes it easier to see and understand the interactions between processes, and to measure where work is building up in the system (look at the queue length). It also allows workload to be distributed across multiple worker threads, which can be beneficial in some systems
well my aim is usually for simplicity so that i can understand my own code later.
I here of multicore microcontrollers and think OK I guess it needs some sort of OS. I get a little confused when you have RTOS available on a fairly simple CPU like AVR or the lower power ARCM like M0(+), I mean threads, one task yielding when it is idle? Any code I have ever written is never "idles" a function is called and it runs and exits, the only waiting is for stuff where you put a while in to test a peripheral register bit usually as a matter of good practice than because you expect to wait several clock cycles for the register to be ready. Things like an ADC I write so that they fire an interrupt when done rather than wait for the ready bit to clear.
It seems to me that the real embedded world has been invaded by people who want to be able to say that they are programming these microcontrollers when in actual fact they are just programmers with OS experience and cannot cope with actually working out how to manage their resources.
so you are executing code, that function running right now has control of the CPU, how does anything else decide to put that function on hold and run another?
My assumption is that the code itself must be written in such a way that it is broken into small function chunks that at the end of each a decision can be made about suspending the whole stream of functions gathered into a task?
My assumption is that the code itself must be written in such a way that it is broken into small function chunks that at the end of each a decision can be made about suspending the whole stream of functions gathered into a task?
Sometimes it is very difficult to do so, and the only solution is to resort to RTOS.
A real life example: ESP8266 chip with non-OS SDK (no RTOS). It is a wifi chip, where "tasks" decide themselves when to yield.
The best way to use an RTOS is by writing each task to co-operate i.e. yield when waiting for something. Then one can often write the whole project as all tasks with idle priority.
Sometimes it is very difficult to do so, and the only solution is to resort to RTOS.
A real life example: ESP8266 chip with non-OS SDK (no RTOS). It is a wifi chip, where "tasks" decide themselves when to yield.
Wifi requires periodic frame exchange to remain on the network. Now, one implements a networking code that uses TLS, and server side uses certificates that require several seconds to handshake. Wifi gets lost when crypto calculation finishes. Long story short: the only solution was to switch to RTOS, because even in the middle of the busy crypto calculation, an RTOS interrupts it and gives time to the low-level WiFi tasks that keeps a connection open.
Would it be possible to keep the firmware no-OS? Theoretically, yes. By using a crypto library that sprinkles yield calls all over the code implementing coroutine-type API. I am not aware of such library. And I think it would be notoriously hard to implement one.
My assumption is that the code itself must be written in such a way that it is broken into small function chunks that at the end of each a decision can be made about suspending the whole stream of functions gathered into a task?
Sometimes it is very difficult to do so, and the only solution is to resort to RTOS.
A real life example: ESP8266 chip with non-OS SDK (no RTOS). It is a wifi chip, where "tasks" decide themselves when to yield.
I think you misunderstood. I meant that to use an RTOS the code must be written in this way. The CPU can only execute what it is being told to so the code must be written in small blocks so that there is a break where the system can decide what to do next.
This is true (required) for any OS. If you are not yielding then the OS has no clue which process really needs time to do something usefull.
.COMMENT \
A SIMPLE REAL-TIME EXECUTIVE (RTX) for Z180/Z280. MUB is true for Z280.
1. Supports any number of tasks (limited to 255 in some places).
2. Total timer interrupt task switch time is less than 60us (Z180/6MHz).
3. Each task must have its own stack.
The RTX has five entry points:
1. Initialisation.
Entered (with JP) just once after power-up, to set-up initial PC and
SP values. This entry kicks-off the RTX and never returns.
2. Timer interrupt (timer tick).
This is the normal entry point. At each timer tick, the RTX switches
to the next task.
3. Switch to next task.
This entry point can be used by the currently executing task to
ask the RTX to switch to the next task, e.g. if the task has nothing
more to do. On the Z180, this entry point is the same as the timer
tick entry point, because the timer int pushes the same things on the
stack as a CALL does.
On the MUB, this entry is implemented as RST 38h, for compact code.
4. Suspend a task (until re-activated).
This 'kills' a task. The RTX will ignore that task, until the task is
re-activated with the function below.
5. Re-activate a (suspended) task.
This re-activates a (previously suspended) task.
For each task, the RTX maintains the data area below:
offset contents initial value
+0 af from table
+2 bc from table
+4 de from table
+6 hl from table
+8 ix from table
+10 iy from table
+12 sp task's sp (*before* the timer interrupt)
+14 pc task's entry point
+16 msr task's MSR value (used with Z280 only)
+18 status 0: task active, 1: task suspended
+19 reserved
The RTX uses push/pop instructions to save/reload registers because these are
probably the fastest way.
The RTX loads iopage to 00h (as part of clearing the timer 0 pending int) - this
is OK because throughout the MUB ints are OFF whenever iopage<>0 and the RTX
should therefore never tick with iopage<>0.
Each task's MSR is preserved, although currently all tasks run with 'ei all_ints'
and preserving MSR is not really necessary; it could just be forced to 'all_ints'
when each task is entered.
\
global REAL_RTX_SWITCH
global RTX_ACTIVATE_ENTRY
global RTX_GET_TASK_STATUS
global RTX_INIT_ENTRY
global RTX_SUSPEND_ENTRY
global RTX_TIMER_INT_ENTRY
; code (all these are RTX tasks)
external BIG_REQUESTS
external FRONT_PANEL
external LANC_OPERATIONS
external MAIN_DATA_LOOP
external NEW_LINKMAPS
external ONESEC
external PROCESS_IPC_BUFS
external PROCESS_OP_TIMEOUTS
external PROCESS_OUT_RCBUFS
external PROCESS_REM_PLOFN
external SC_EAROM_UPDATE
external SETUP_MODE
; data
external HL_SAVE
external PC_SAVE
external RTX_ALL_SUSPENDED
external RTX_CURRENT_TASK
external RTX_DATA_AREAS
external RTX_DATA_PTR
external RTX_TIMER
external MSR_SAVE
external SP_SAVE
external STACK_START
INCLUDE DEFS.MUB
SECTION CODE,X
MUB EQU TRUE
; RTX initialisation table. Terminated by a line with PC=0.
; In the future, this should be modded to hold ix,iy,sp,pc,msr values only.
; All tasks have equal priority and execute on a round-robin basis.
; ** The task *order* (i.e. their #s) is assumed by calls to rtx_activate & rtx_suspend **
; ** THEREFORE: ADD NEW TASKS ONLY AT THE *END* OF THE TABLE **
; ** Dont forget to EQUate rtx_tasks in defs.mub to #tasks below (incl PC=0 task) **
RTX_INITIAL_REGS: ; task# v
; v
; AF BC DE HL IX IY SP PC MSR stat+res v
DW 0 0 0 0 0 0 STACK_START+(1*STACK_SIZE) MAIN_DATA_LOOP ALL_INTS 0 ; 0
DW 0 0 0 0 0 0 STACK_START+(2*STACK_SIZE) LANC_OPERATIONS ALL_INTS 0 ; 1
DW 0 0 0 0 0 0 STACK_START+(3*STACK_SIZE) BIG_REQUESTS ALL_INTS 0 ; 2
DW 0 0 0 0 0 0 STACK_START+(4*STACK_SIZE) SC_EAROM_UPDATE ALL_INTS 0001H ; 3*
DW 0 0 0 0 0 0 STACK_START+(5*STACK_SIZE) NEW_LINKMAPS ALL_INTS 0001H ; 4*
DW 0 0 0 0 0 0 STACK_START+(6*STACK_SIZE) SETUP_MODE ALL_INTS 0 ; 5
DW 0 0 0 0 0 0 STACK_START+(7*STACK_SIZE) ONESEC ALL_INTS 0001H ; 6*
DW 0 0 0 0 0 0 STACK_START+(8*STACK_SIZE) FRONT_PANEL ALL_INTS 0001H ; 7*
DW 0 0 0 0 0 0 STACK_START+(9*STACK_SIZE) PROCESS_OP_TIMEOUTS ALL_INTS 0001H ; 8*
DW 0 0 0 0 0 0 STACK_START+(10*STACK_SIZE) PROCESS_OUT_RCBUFS ALL_INTS 0001H ; 9*
DW 0 0 0 0 0 0 STACK_START+(11*STACK_SIZE) PROCESS_IPC_BUFS ALL_INTS 0001H ;10*
DW 0 0 0 0 0 0 STACK_START+(12*STACK_SIZE) PROCESS_REM_PLOFN ALL_INTS 0001H ;11*
DW 0 0 0 0 0 0 0 0 0 0
; (*) the above tasks such marked run only once and suspend themselves, and are
; re-activated by some event. The '0001H' value sets status=01h; this makes
; the task *initially* suspended (e.g. we *dont* want to update the eeprom at
; every power-up, do we?).
; RTX initialisation/entry point.
RTX_INIT_ENTRY:
DI ; Necessary ! (in case of entry with EI)
; Copy initial register values from ROM into RAM.
LD HL, RTX_INITIAL_REGS
LD DE, RTX_DATA_AREAS
LD BC, RTX_TASKS*RTX_DATA_SIZE
LDIR
; Reset ptr (IX) to base+20 of the first task
LDW (RTX_DATA_PTR), RTX_DATA_AREAS+RTX_DATA_SIZE
LD (RTX_TIMER), RTX_TC ; RTX downcounter time constant
LD (RTX_CURRENT_TASK),0 ; Initial task # := 0
; Enable timer interrupt
IF MUB
; (no action necessary because timer 0 int is already always enabled
; at the timer, and rtx1st does an EI etc)
ELSE
IN0A TCR
SET 5, A ; Timer 1 ints (RTX) ON
OUT0A TCR
ENDIF
; Set-up SP to 1st POP task's initial values and jump to 1st task
LD SP, RTX_DATA_AREAS
JP RTX1ST ; (also does EI)
; This is the timer interrupt service routine which switches tasks.
; At entry here (and at exit) rtx_data_ptr points to the 1st byte after the
; task's data area (i.e. byte @base+20). Therefore, when we load SP from
; rtx_data_ptr and start PUSHing the registers, the first word pushed (MSR)
; is written in the right place. Rtx_data_ptr does NOT point at base+0.
RTX_TIMER_INT_ENTRY:
; First, preserve the regs which we are going to corrupt, etc, while
; adjusting SP back to its value *before* the timer interrupt.
; All this must be done with instructions which dont modify flags.
; We get here from ct0 interrupt, with AF'.
IF MUB
LD (RTX_TIMER), RTX_TC ; Reload the down-counter
EXX
IOPAGE 0FEH ; Clear CC bit (reset 'int pending')
LD A, 11100000B ; (this could be done *after* the RTX code
OUT (0E1H), A ; but for some reason doing it there did not
IOPAGE 0 ; work properly; it does not really matter)
EXX
EX AF, AF' ; and switch back to main AF
INC SP ; Skip over Mode 3 Int Reason Code
INC SP ; (always the same - boring)
LD (HL_SAVE), HL ; Save HL of interrupted task
POP (MSR_SAVE) ; Save MSR of interrupted task
; This entry point is used by rtx_switch, which has already loaded
; HL and MSR into hl_save and msr_save, and disabled all interrupts.
RTX_ES: POP HL ; HL := PC of interrupted task
LD (SP_SAVE), SP ; Save SP (the value *before* the timer int)
ELSE
LD (HL_SAVE), HL ; Save HL of interrupted task
POP HL ; HL := PC of interrupted task
LD (SP_SAVE), SP ; Save SP of interrupted task
ENDIF
; HL now holds the interrupted task's PC value.
; We save the interrupted task's registers by a series of PUSHes.
LD SP, (RTX_DATA_PTR) ; Set SP to interrupted task's data area
IF MUB
DEC SP ; Skip over status+reserved bytes
DEC SP
PUSH (MSR_SAVE) ; Save MSR
PUSH HL ; Save PC
PUSH (SP_SAVE) ; Save SP (the value *before* this interrupt)
PUSH IY ; Save IY
PUSH IX ; Save IX
PUSH (HL_SAVE) ; Save HL
ELSE
DEC SP ; Skip over status+reserved bytes
DEC SP
DEC SP ; Z180: 'push' a dummy value instead of MSR
DEC SP
PUSH HL ; Save PC
LD HL, (SP_SAVE)
PUSH HL ; Save SP
PUSH IY ; Save IY
PUSH IX ; Save IX
LD HL, (HL_SAVE)
PUSH HL ; Save HL
ENDIF
PUSH DE ; Save DE
PUSH BC ; Save BC
PUSH AF ; Save AF
; Interrupted task is now saved. Go to next task.
; If next task's PC=0, this is the end of the task table and we wrap.
LD B, RTX_TASKS ; Max # of tasks to go through
RTX_NX: LD IX, (RTX_DATA_PTR)
LD SP, IX ; This loads SP just right for the POPs below
LD DE, RTX_DATA_SIZE
ADD IX, DE ; Move rtx_data_ptr to next task's data area
LD HL, RTX_CURRENT_TASK
INC (HL) ; Current task # ++
IF MUB
LD DE, (IX-6) ; Check if next task's PC=0
LD A, D
OR E
ELSE
LD A, (IX-6)
OR A, (IX-5)
ENDIF
JR NZ, RTX_01 ; NZ: no, continue
LD IX, RTX_DATA_AREAS+RTX_DATA_SIZE ; else reset ptr to 1st task
LD SP, RTX_DATA_AREAS ; and set SP to start POPping for 1st task
LD (HL), 0 ; and reset 'current task #'
RTX_01: LD (RTX_DATA_PTR), IX
LD A, (IX-2) ; Now check that the newly-selected task is
OR A ; not suspended.
JR Z, RTX1ST ; Z: not suspended - continue
DJNZ RTX_NX ; else loop through all the tasks in the table
; (If all tasks are suspended, we fall out here and the next task gets
; executed anyway, which does not really matter unless one actually
; relies on suspended tasks to *never* execute. Doing this properly
; is more complicated).
LD A, 1 ; Mark 'all suspended' condition detected
LD (RTX_ALL_SUSPENDED), A
; Load registers for next task.
; This is also the entry point for starting the RTX (enter with
; SP = base of 1st task's data area)
RTX1ST: POP AF ; Load AF
POP BC ; Load BC
POP DE ; Load DE
IF MUB
POP (HL_SAVE) ; Recover HL
POP IX ; Load IX
POP IY ; Load IY
POP (SP_SAVE) ; Recover SP
POP (PC_SAVE) ; Recover PC
POP HL ; Recover MSR
ELSE
POP HL
LD (HL_SAVE), HL
POP IX
POP IY
POP HL
LD (SP_SAVE), HL
POP HL
LD (PC_SAVE), HL
ENDIF
LD SP, (SP_SAVE) ; Load SP
; Clear the pending interrupt, enable ints and enter the new task.
IF MUB
PUSH (PC_SAVE) ; Put PC on stack (for RETIL to pop-off)
PUSH HL ; Likewise with MSR
LD HL, (HL_SAVE) ; Load HL
RETIL ; Enter new task
ELSE
LD HL, (PC_SAVE)
PUSH HL ; Put PC on stack (for RET to pop-off)
LD HL, (HL_SAVE) ; Load HL
EX AF, AF'
IN0A TCR ; Clear the pending timer 1 interrupt bit
IN0A TMDR1L
EX AF, AF'
EI ; Re-enable interrupts
RET ; Enter new task
ENDIF
; CALL this entry point to cause a switch to the next task. Done via RST 38H.
; This entry also reloads the RTX tick down-counter, so that the next RTX tick
; cannot occur until after a whole tick period.
REAL_RTX_SWITCH:
IF MUB
DI ; Prevent 'normal' RTX tick coming in here
PUSH AF
LD A, (RTX_TIMER) ; If rtx is NOT disabled,
INC A
JR Z, RRS_NL
LD (RTX_TIMER), RTX_TC ; then reload its down-counter
RRS_NL: PUSH BC
LD (HL_SAVE), HL ; Save HL straight into RTX's HL save location
LD C, 0
LDCTL HL, (C) ; Read current MSR
LD A, L
OR ALL_INTS ; Correct for 'di' above having cleared the EI bits
LD L, A
LD (MSR_SAVE), HL ; Save MSR straight into RTX's MSR save location
POP BC
POP AF
JP RTX_ES ; Do (nearly) as if a timer tick occured
ELSE
DI
EX AF, AF'
LD A, RTX_TC ; Reload RTX downcounter time constant
LD (RTX_TIMER), A
EX AF, AF'
JP RTX_TIMER_INT_ENTRY ; Do as if timer tick occurred
ENDIF
; CALL this entry point to suspend a task.
; Enter with E = task # (0..254).
; If E=255, then the task currently executing is suspended. This feature
; is useful if a task does not know its own task # (i.e. 'suspend me').
; ALSO, READ THE LARGE COMMENT BELOW. It effectively means that if a routine
; wants to suspend itself, it must use the 'task#=255' call, NOT a 'task=own#'
; call !!
; Kills DE,HL.
RTX_SUSPEND_ENTRY:
LD D, E ; Make a copy of task#
INC E ; If E=0FFh
JR NZ, RSE_05
LD A, (RTX_CURRENT_TASK) ; then use the 'current' task # instead
LD E, A
INC E
RSE_05: DEC E
IF MUB
LD A, RTX_DATA_SIZE ; Index to the task's data area
MULTU A, E
ADDW HL, RTX_DATA_AREAS+18
ELSE
PUSH DE
LD D, RTX_DATA_SIZE
MULDE
LD HL, RTX_DATA_AREAS+18
ADD HL, DE
POP DE
ENDIF
LD (HL), 1 ; and mark it 'suspended'
; If entry task# = 255, then we have a 'suspend me' call, and the
; task is suspended IMMEDIATELY. Normally, this is what would be
; really required. However, if task#<>255, then we have a case where
; one task (or int routine, etc) is suspending another task, and we
; do NOT then perform a rtx_switch. Doing an rtx_switch makes sense
; only on a 'suspend me' request, not on a 'suspend task n' request.
INC D
CALL Z, REAL_RTX_SWITCH ; and switch immediately to next task
RET ; Return via here when task is re-activated
; CALL this entry point to re-activate a task.
; Enter with E = task # (0..).
; Kills AF,HL (+DE on Z180).
RTX_ACTIVATE_ENTRY:
IF MUB
LD A, RTX_DATA_SIZE ; Index to the task's data area
MULTU A, E
ADDW HL, RTX_DATA_AREAS+18
ELSE
LD D, RTX_DATA_SIZE
MULDE
LD HL, RTX_DATA_AREAS+18
ADD HL, DE
ENDIF
LD (HL), 0 ; and mark it 'active'
RET
; CALL this entry point to find out whether a task is currently activated (not
; suspended).
; Enter with E = task # (0..).
; Returns A=00 if activated, 01 if suspended.
; Kills AF,HL (+DE on Z180).
RTX_GET_TASK_STATUS:
IF MUB
LD A, RTX_DATA_SIZE ; Index to the task's data area
MULTU A, E
ADDW HL, RTX_DATA_AREAS+18
ELSE
LD D, RTX_DATA_SIZE
MULDE
LD HL, RTX_DATA_AREAS+18
ADD HL, DE
ENDIF
LD A, (HL) ; and get the 'status' byte
RET
IMHO this is a misconception. After all: how does the OS knows that something needs to be done? It has to test whether something happened!
I don't think I follow. Ever heard of interrupts? What's the problem? The task which is interrupted does not need to know about the existence of interrupts, or the fact it's being interrupted. Nothing needs to be "sprinkled" in crypto processing code.
Not true, for preemptive RTOSs where a task can be suspended between any two instructions.
The RTOS contains a scheduler, whose job is to determine which task should be executing at any given instant. The scheduler is not executed unless there is a reason to re-determine which task should be running. Hence until something happens, the current task will continue executing.
I don't think I follow. Ever heard of interrupts? What's the problem? The task which is interrupted does not need to know about the existence of interrupts, or the fact it's being interrupted. Nothing needs to be "sprinkled" in crypto processing code.
Exactly, that's what an RTOS does. It installs a timer interrupt handler, which saves current tasks's registers and switches to another task.