views:

1124

answers:

10

How does one create threads that run in parallel while programming PIC18, since there is no OS?

+6  A: 

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.

Mark Rushakoff
+7  A: 

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.

iWerner
+5  A: 

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.

unwind
+10  A: 

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.

janm
Agreed. If it meets your needs, keep it simple.
Steve Melnikoff
Note that this assumes asynchronous I/O. It doesn't help if your main program is blocked on some I/O. With threads, and simple time based round robin taskswitching you uncouple various tasks, guaranteeing them a small, but constant amount of time.
Marco van de Voort
This is a PIC18 chip, and threads need to be implemented somewhere and their state needs to be stored. Pretty much all of the I/O you can do can be done asynchronously in some way; if you start doing synchronous I/O, you have to disable interrupts for reliability and then you're not going to be preempted anyway. You can implement a threading library and scheduler on top of the async features of the chip, but why bother? You probably have less than 4kB of RAM and the time-critical operations are likely to be I/O driven. Just consider PIC18 as a h/w implemented, very simple OS.
janm
+2  A: 

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.

ndim
+1  A: 

You might want to read this article from embedded system programming: Build a Super Simple Tasker

simon
+1  A: 

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).

Jeanne Pindar
Can CCS be used for PIC18?
Alphaneo
Yes, it supports PIC18s. See: http://www.ccsinfo.com/ccs-product-catalog.php
Jeanne Pindar
A: 

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.

http://www.mculabs.com/drivers/qtask.html

mr405fwy
A: 

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".

David Cary
A: 

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.

supercat