How does one create threads that run in parallel while programming PIC18, since there is no OS?
You can put an RTOS on there (there's an unofficial ucOS port, or you could check out FreeRTOS's PIC18 port).
Otherwise, you could try implementing coroutines in C by using setjmp
and longjmp
.
You could try Cooperative multitasking.
For types of problems that PICs solve, you'd probably be better of if you try a different design that uses interrupts or polling instead of multiple threads.
If there is no OS at all, you'll (obviously) have to re-create the necessary functionality yourself.
The easiest way to follow would probably be to install a timer interrupt running at some suitable frequency (probably depends on your actual clock speed, but perhaps in the range 100-1000 Hz). In the interrupt handler, you need to inspect the state of the current thread, and decide if a switch should occur.
The trick is then to make the switch if necessary, and return from the interrupt handler into a different thread.
Of course, getting this to work when the threads themselves might use interrupts, will not necessarily be easy.
You could also look into installing some kernel, perhaps Contiki.
Here is an example of "protothreads" for the PIC18, looks like a reasonable amount of code. Not sure about the semantics, though.
Update: This will probably require you do some of the lowest-level code in assembler (I'm not sure, haven't worked in C on the PIC so I don't know exactly how much control you get). You will need control over the registers the program counter, and those are not C concepts.
Don't use threads, use an event loop.
The PIC18 is a small processor and an event loop based style means you don't have to keep many deep stacks hanging around. You need to write your code in terms of the event loop, but that is probably reasonable.
If you do have some long running tasks, use timers are different interrupt priority levels to allow higher priority event loops to preempt lower priority event loops, and put appropriate types of work into the appropriate event queue.
Be aware that on microcontrollers, some "threads" can also be handled by just some specific interrupt handler, and thus run in "parallel" to your main event loop anyway.
E.g. if you have an external event trigger an ADC conversion, your ADC-conversion-done handler can take that value, do a few calculations and then set some output bits to adapt the control output according to the ADC value. All that can happen in the interrupt handler, and thus parallel to everything else.
Depending on the things you need to do in parallel, you can choose a combination of multiple techniques to make stuff work in parallel as intended.
You might want to read this article from embedded system programming: Build a Super Simple Tasker
The CCS compiler includes an RTOS. I haven't used it, but from the compiler manual:
The CCS Real Time Operating System (RTOS) allows a PIC micro controller to run regularly scheduled tasks without the need for interrupts. This is accomplished by a function (RTOS_RUN()) that acts as a dispatcher. When a task is scheduled to run, the dispatch function gives control of the processor to that task. When the task is done executing or does not need the processor anymore, control of the processor is returned to the dispatch function which then will give control of the processor to the next task that is scheduled to execute at the appropriate time. This process is called cooperative multi-tasking.
Just a word of warning - check their forums for info about the specific features you're looking for. Apparently CCS has a habit of releasing new features before they're fully tested. That's one reason I'm still using the older version (v3.249).
This does that very thing, a task loop, as well as provisions priorities of tasks, and what I like simple coding of breaking up long running functions into slices.
I agree with ndim -- you can think of each interrupt handler as something like a "thread". Sometimes all the tasks you need to do can be handled by interrupt handlers triggered by external and internal events -- the "main loop" is an idle loop that does nothing.
I don't know where some commenters get the idea that there is "no OS" for the PIC18. There are quite a few PIC-specific multithreading libraries and "multitasking operating system kernels" for the PIC18, many of them free and open source. See PICList: "PIC Microcontroller specific Multitasking Methods".
On the 8051, I've done dual-tasking by using a simple stack switcher. I would expect the same could be done on the PIC, provided each task only used 16 levels of stack. The code would be something like this (assumes _altSP is in the common bank)
_InitTask2: movff _STKPTR,_altSP movlw 16 movwf _STKPTR,c goto _Task2Start _TaskSwitch: movf _altSP,w,c movff _STKPTR,_altSP movwf _STKPTR,c return
The main task should call _InitTask2 to start the second task. The second task will run until it calls _TaskSwitch, whereupon the main task will resume execution following the instruction that called _InitTask2. From thence forth, every time a task calls _TaskSwitch, the other task will resume execution from the last place it called _TaskSwitch.
If you use this approach, your compiler will have to be informed that all registers may be trashed by calls to _InitTask2 or _TaskSwitch. It will also have to be told that _Task2Start and functions it calls must be allocated separate variable space from the main task.
I'm not sure what you need to tell the compiler to make it happy, but I will say that cooperative dual-tasking can make some things work really nicely.