Page 1 of 1

Multi-tasking

Posted: Sun Jan 06, 2019 6:03 pm
by basicchip
While I tend to favor interrupt drive or callbacks, both are possible in BASIC. One user seemed determine to get multitasking into the BASIC. After some thinking on my part, I could see interrupts could be used to switch between tasks. So I took a look at a simple example. Using timer interrupts and separate stacks for each task, it is possible to time share them. These tasks are ones that never end. For tasks that do end, they can still be driven by interrupts and will have a higher priority if only because the time shared tasks are not blocking interrupts.

This was a first, I posted a blog entry before the forum entry.

https://www.coridium.us/coridium/blog/m ... ic-why-not

Usually deeper details will be at the forum, and discussion can happen here with users. The blog is driven by markdown, so I can post user comments to the blog, if they are in markdown.

This task switch was done with 4 lines of inline assembly, needed to copy the SP or to set the SP to the new context.

Source for the simple example below, inline ASM macros here viewtopic.php?f=9&t=1303 --

Code: Select all

#ifdef LPC1756
 #include "LPC17xx.bas"
#elif defined LPC11U37
 #include "LPC11U3x.bas"
#elif defined LPC54102
 #include "LPC54102.bas"
#else
 #error this CPU not supported yet
#endif

#include "ASMinlineBASIC.bas"

#define MAX_TASK	4
#define INT_FRAME	(17-1)			' 8 registers + lr INTSUB pushes + 8 registers of INT

#define PT_PC		(INT_FRAME-1)

dim taskSP 	(MAX_TASK-1)			' saved SP here
dim taskTIME(MAX_TASK-1)			' in msec

task_num = 0		' cycles through the tasks

INTERRUPT SUB TIMER1IRQ
	dim x			
	
	if (task_num = -1) then	
		' special case on startup -- don't save the main SP
	else
		x = ADDRESSOF(taskSP) + (task_num<<2)
		ASM_MOVRloRhi(0,REG_SP)		' r0 now has value of SP
		ASM_STRoff(0,7,0)			' save it in taskSP(tasknum)
	endif
	
	task_num += 1
	if task_num = MAX_TASK then task_num = 0
	
	x = ADDRESSOF(taskSP) + (task_num<<2)
	ASM_LDRoff(0,7,0)				' get it from taskSP(tasknum)
	ASM_MOVRhiRlo(REG_SP,0)			' move SP to next task stack

	T1_TC  = 0      				' otherwise might be past MR0
	T1_MR0 = taskTIME(task_num)-1	' set the time alotted to the task
	
	T1_IR = 1	      ' Clear interrupt	
	
ENDSUB

SUB ON_TIMER ( max_cnt, dothis )		' max_cnt in msec
#if defined LPC1756 || defined LPC1751
	TIMER1_ISR   = dothis + 1              	'set function of VIC   -- need the +1 for Thumb operation
	T1_PR  = 24999                         	'1 ms prescale
	VICIntEnable or= (1<<2)  				'Enable interrupt
#elif defined LPC11U37
	TIMER1_ISR   = dothis + 1              	'set function of VIC   -- need the +1 for Thumb operation
	SYSCON_SYSAHBCLKCTRL OR= (1<<10)		' enable TIMER1
	T1_PR  = 24999                         	'1 ms prescale
	VICIntEnable OR= (1<<TIMER1_IRQn)  	 	'Enable interrupt
#elif defined LPC54102
	TIMER1_ISR   = dothis + 1              	'set function of VIC   -- need the +1 for Thumb operation
	ASYNCAPBCLKCTRLSET = SYSCON_CLOCK_TIMER1		' enable TIMER1
	T1_PR  = 12499                         	'1 ms prescale
	VICIntEnSet0 = (1<<TIMER1_IRQn) 	 	'Enable interrupt
#else
	#warning -- need special setup for this CPU
#endif

	T1_MR0 = max_cnt-1 		' set up match number of ms
	T1_MCR = 3      		' Interrupt and Reset on MR0
	T1_IR  = 1      		' clear interrupt
	T1_TC  = 0      		' clear timer counter
	
	T1_TCR = 1      		' TIMER1 Enable
	
ENDSUB


#define STACK_SZ	127	' probably don't need this much room -- each task needs a seperate stack
DIM stack0 (STACK_SZ)		
DIM stack1 (STACK_SZ)		
DIM stack2 (STACK_SZ)		
DIM stack3 (STACK_SZ)		

'	tasks can not share subroutines/functions that have parameters or locals, as those are NOT re-entrant



sub task0
	while 1					' tasks SHALL NEVER return -- equivalent of crossing the streams
		IO(6)=0
		IO(6)=1
	loop
							' if we do finish early someday -- a software interrupt could be used ???
end sub

sub task1
	while 1
		IO(7)=0
		IO(7)=1
	loop
end sub

sub task2
	while 1
		IO(8)=0		
		IO(8)=1
	loop
end sub

sub task3
	while 1
		IO(9)=0
		IO(9)=1
	loop
end sub


main:
	
	task_num = -1		' special case for initial task
	
	taskTIME(0) = 50		' time in msec for each task
	taskTIME(1) = 10
	taskTIME(2) = 15
	taskTIME(3) = 20
	
	taskSP(0) = ADDRESSOF(stack0) + (STACK_SZ-INT_FRAME)*4 
	stack0(STACK_SZ)   = &H61000000					'  seems to be initial PSR
	stack0(STACK_SZ-1) = ADDRESSOF(task0) + 1
	stack0(STACK_SZ-8) = &HFFFFFFF9					' INTERRUPT SUB pushes LR here
		
	taskSP(1) = ADDRESSOF(stack1) + (STACK_SZ-INT_FRAME)*4 
	stack1(STACK_SZ)   = &H61000000					'  seems to be initial PSR
	stack1(STACK_SZ-1) = ADDRESSOF(task1) + 1
	stack1(STACK_SZ-8) = &HFFFFFFF9					' INTERRUPT SUB pushes LR here
		
	taskSP(2) = ADDRESSOF(stack2) + (STACK_SZ-INT_FRAME)*4 
	stack2(STACK_SZ)   = &H61000000					'  seems to be initial PSR
	stack2(STACK_SZ-1) = ADDRESSOF(task2) + 1
	stack2(STACK_SZ-8) = &HFFFFFFF9					' INTERRUPT SUB pushes LR here
		
	taskSP(3) = ADDRESSOF(stack3) + (STACK_SZ-INT_FRAME)*4 
	stack3(STACK_SZ)   = &H61000000					'  seems to be initial PSR
	stack3(STACK_SZ-1) = ADDRESSOF(task3) + 1
	stack3(STACK_SZ-8) = &HFFFFFFF9					' INTERRUPT SUB pushes LR here

	ASYNCAPBCLKCTRLSET = SYSCON_CLOCK_TIMER1		' enable TIMER1	
	
	ON_TIMER (taskTIME(0), ADDRESSOF(TIMER1IRQ))
		
	ASYNCAPBCLKCTRLSET = SYSCON_CLOCK_TIMER1		' enable TIMER1	
	print hex(ASYNCAPBCLKCTRL), hex(SYSCON_CLOCK_TIMER1)
	
	while 1			' never comes back here after the first timer IRQ
	loop

Re: Multi-tasking

Posted: Mon Jan 07, 2019 1:51 am
by basicchip
This was the first pass, waiting to get feedback.

The task0 could be setup to use the main stack, eliminating stack0, and by disabling the timer, it could return to the main: or wherever it was called from.

Not sure this is a real necessary improvement, but could be done. For simplicity push ASM instructions would be needed.

Re: Multi-tasking

Posted: Fri Mar 22, 2019 11:43 am
by BryantRen
Did you play around with this idea any further, Basicchip?

Re: Multi-tasking

Posted: Fri Mar 22, 2019 2:50 pm
by TodWulff
BryantRen wrote:
Fri Mar 22, 2019 11:43 am
Did you play around with this idea any further, Basicchip?
Good day, BryantRen.

Sir B has been very helpful in assisting me/us with implementing a working solution.

After a couple of iterations of context switch code massages, it seems to be quite stable.

I'm working on a couple of projects that will lean on what I am coining as ABmt (ArmBasic MultiTasking).

Those projects will eventually include:
  • A pendant controller (think VR Headset Control) for a small boat's trolling motor.
  • Touch Screen LCD Weather Station (with LORA/SIGFOX connectivity to remote sensors)
  • Wireless Model Rocket Launcher
  • A SDR (Software Defined Radio)
  • ADS-B sniffer (the day job is Aerospace/Defense, so this rings true on the value scale)
  • IMU for a maritime autopilot (building on the controller above) using BST goods
  • ... and more, until I am no longer vertical and ventilating
ABmt is what I perceive as a natural progression of the initial implementation that enables task addition with low-effort compile-time self-configuration/ingestion of task code, etc. It is a precursor to a proper implementation of an RTOS, ABrtos (a.k.a. Project Storm), where a formal scheduler will be implemented to enable proper run-time task sequencing/scheduling via some form of structured approach (rate monotonic, preemptive, cooperative, ...). ABmt implements a fixed hybrid form of Cooperative/Preemptive, where tasks yield their time slice when they haven't anything to accomplish during the current pass, with unilateral/preemptive task switching in the event a task doesn't yield prior to its time slice being consumed.

Anyways, that is getting down into the weeds a bit. Here is a screen shot of an early implementation of ABmt:

Image

There is a dprintf debug serial stream that is bitbanged and is able to emit info at some 4.4Mbps via a gpio on a Coridium 54102 target (bit banged to mitigate impact of interrupts inside an ISR). The emissions in the image are highly verbose, just for testing/dev purposes.

The UI that you're seeing in the image is a project I've been working on, with help from BasicChip and Olzeke51. As a small bit of a spoiler alert, this will be a topic of a blog post where it is formally introduced, with links to an introductory video(s), and instantiation of a thread herein to offer it up to the user community.

I've gotta run for the day job. Take care and have a great day.

-t

Re: Multi-tasking - JUST CURIOUS

Posted: Sat Aug 31, 2019 2:12 am
by olzeke51
OFF HAND - - - Do you know why there are so many 'Power ON' messages on a multi-tasking program
when you use BT<button> to <STOP> it ??. . . . JUST CURIOUS -( not concerned about the number of blank lines)
My BT window copy::
'
83400 34794 83460 34794 83520 34794 83580 34858 <<< PROGRAM DATA

*****PUSHED THE <STOP> BUTTON IN BT

Welcome to ARMbasic Kernel[8.40c] with Floating Point Copyright 2018, Coridium Corp.
for the LPC11U37

.... [5 blank lines]
Welcome to ARMbasic Kernel[8.40c] with Floating Point Copyright 2018, Coridium Corp.
for the LPC11U37

.... [5 blank lines]
Welcome to ARMbasic Kernel[8.40c] with Floating Point Copyright 2018, Coridium Corp.
for the LPC11U37
[2 blank lines]
toggleReset <<< BT TRIGGERED <<< BT TRIGGERED <<< BT TRIGGERED <<< BT TRIGGERED
[4 blank lines]
Welcome to ARMbasic Kernel[8.40c] with Floating Point Copyright 2018, Coridium Corp.
for the LPC11U37
[4 blank lines]
Welcome to ARMbasic Kernel[8.40c] with Floating Point Copyright 2018, Coridium Corp.
for the LPC11U37
[6 blank lines]
Welcome to ARMbasic Kernel[8.40c] with Floating Point Copyright 2018, Coridium Corp.
for the LPC11U37
[5 blank lines]
Welcome to ARMbasic Kernel[8.40c] with Floating Point Copyright 2018, Coridium Corp.
for the LPC11U37
[7 blank lines]
Welcome to ARMbasic Kernel[8.40c] with Floating Point Copyright 2018, Coridium Corp.
for the LPC11U37

.................... this part is normal
stopButton <<***SECOND <STOP> BUTTON A COUPLE OF SECONDS LATER
[5 blank lines]
toggleReset





Welcome to ARMbasic Kernel[8.40c] with Floating Point Copyright 2018, Coridium Corp.
for the LPC11U37

Re: Multi-tasking

Posted: Sat Aug 31, 2019 2:53 am
by basicchip
Not seeing that here with the stock Multitask example. But looks like another interrupt active, though I am surprise it would act quite like that.

Re: Multi-tasking

Posted: Tue Oct 22, 2019 8:09 pm
by basicchip
Multitasking like interrupts have some limitations.

You should not do WAIT or WAITMICRO as the task switch might occur in the middle of the wait. But you can read the TIMER and look for elapsed time up to 35 seconds. It is beat to exit the task rather than waiting in a loop.

Code: Select all

sub task3
	dim startTime
	dim toggle
	toggle = 0
	startTime = TIMER
	while 1
		if TIMER - startTime > 1000000 then
			toggle = not toggle
			startTime = TIMER
		endif
		IO(1)=toggle
		VICIntSetPend0 = (1<<TIMER1_IRQn) ' force Timer 1. Interrupt -- ending this task slot
	loop              ' when this task is executed again it starts here inside the while 1 loop
end sub
PRINT and PRINTF are not re-entrant so should be avoided in more than one task, without disabling the task switch interrupt.

While TXD would work, multiple tasks may jumble the output.

There is only one STRING accumulator, so if more than one task are doing STRING operations, you need to disable interrupts around that operation.

Floating point operations are also not re-entrant so should not be done in interrupts, or in multitasking you need to disable the task switch interrupt while they are being done.

For multitasking using TIMER1 for task switch --

Code: Select all

  VICIntEnClear0 = (1<<TIMER1_IRQn) 	 	'Disable interrupt
	
  ' ... code that can not be interrupted -- print, prtinf, sprintf, string, floating point
  
  VICIntEnSet0 = (1<<TIMER1_IRQn) 	 	'Enable interrupt