EmbeddedRelated.com
Forums
The 2024 Embedded Online Conference

Multitasking with Costates and Cofunctions in Dynamic C

Started by ian_d_chapman March 19, 2004
Hi!

I'm new to this forum, and about to take my first hesitant steps
with the Rabbit and Dynamic C. I have an RCM3600 development kit on
order and should receive it next week. In the meantime, I have been
reading the on-line documentation and trying to figure out how best
to implement a multitasking design using Dynamic C.

I'll try to explain my initial thoughts below. I'd greatly
appreciate any steers on the best way to proceed and any potential
pitfalls (especially undocumented ones!).

My application needs to control two identical devices concurrently
(each connected to its own serial port). For each device, there is
an "initialisation" phase and a "run" phase. I'm thinking of using
code along the following lines:

main()
{
while (1)
{
costate // Device 0
{
wfd cof_dev_init(0);
wfd cof_dev_run(0);
}
costate // Device 1
{
wfd cof_dev_init(1); // Re-use functions
wfd cof_dev_run(1); // <-
}
// Other costates to be added
}
}

cofunc void cof_dev_init(int i)
{
auto int a_var; // Separate instances?
static int s_var[2]; // Must be indexed by i

cof_send_string(i, "Hello!"); // Needs to yield or wait
waitfor(DelayMs(500));
flip_output(i); // Instant completion
}

// ... etc. ...

At this stage, I have the following specific questions:

1. Will this approach work? In particular, is it okay to nest
cofunctions in this way (i.e. cof_send_string() inside cof_dev_init
() inside the costates for devices 0 and 1)? Is there a heavy
penalty in terms of memory usage or performance?

2. Am I correct in assuming that cof_dev_init() and cof_dev_run()
cannot be single-user cofunctions, because two instances of each
need to run concurrently for devices 0 and 1?

3. Is it true that independent instances of the "a_var" will be
automatically created for cof_dev_init(0) and cof_dev_init(1)? I
assume that this isn't true for "s_var", though.

4. Would it be better to change cof_dev_init() and cof_dev_run()
into indexed cofunctions (i.e. cof_dev_init[i]() and cof_dev_run[i]
())? If so, then:

(a) How is the indexed cofunction declared? For example, is
it "cofunc void cof_dev_init[int i](void)"?

(b) Is the value of index i available inside the function so that it
can be passed as a parameter to cof_send_string() and flip_output()?

Many thanks in advance for any pointers.

All the best.
--
Ian Chapman
Chapmip Technology


If you call a costate from more than one place, such as cof_dev_run, you
must give each instance an index. With cofunctions, DC creates separate
static storage for auto variables and parameters. This is not normal C
syntax. Costates work by static storage of the auto variables and
parameters, as well as where they are when they yield; they do not use the
stack. So, you would want

while (1)
{
costate // Device 0
{
wfd cof_dev_init[0]();
wfd cof_dev_run[0]();
}
costate // Device 1
{
wfd cof_dev_init[1](); // Re-use functions
wfd cof_dev_run[1]();
}
// Other costates to be added
}
}

It would probably crash the program to call the same non-indexed cofunction
from more than one place in a program. With indexed cofunctions, each
index number can only be called from one place in a program, but that one
place can call more than one indexed cofunction

for(x=0; x<10; ++x) {
wfd cof_my_func[x]();
}

However, doing this will cause only one function to run at a time until
completion. Doing this will allow all 10 identical functions to run
simultaneously:

for(x=0; x<10; ++x) {
costate {
wfd cof_my_func[x]();
}
}

Confused, or not? Maybe the underlying priciple will help. Cofunctions
are an invention of ZWorld to cooperative multitasking. Instead of stack
juggling, which is the traditional method, they multitask without using the
stack at all. Every cofunction's auto variables as well as static
variables are given static storage, so there is no difference between the
two if the cofunction isn't indexed. When yield is encountered, the
calling costate or cofunction has static storage set aside for where the
processor was; this is where it returns the next time that costate is
encountered; do you see how this avoids multiple stacks? When it returns,
since the local variables and function parameters are given static storage,
it simply tells the processor to jump to thus and so location and there is
no stack variables to worry about.

Pitfalls:
(1) Calling the same cofunction from more than one place.
(2) Instead of naming costates and starting and stopping, using an if/then
as follows:
if (Task2Running) {
costate {
wfd cof_Task2();
}
}
Instead, do this:
costate Task2 {
wfd cof_Task2();
}
By giving it a name, you turn it on and off by using the name.
(3) Nested costates and cofunctions. Don't start a costate loop inside a
cofunction.
(4) Not putting your costates in an organized loop in main
(5) Putting significant code in a costate instead of cofunctions. I would
structure it this way:
for(;;) {
costate {
wfd Task1();
}
costate {
wfd Task2();
}
}
(6) Calling a cofunction from more than one costate. There is the scofunc
that allows this; it senses if a scofunc is busy and makes all other
cofunc's calling it wait until it finishes and then the next in line gets
to call that function. I prefer indexed cofunctions and making each task
keep track of its number over using scofunc's, but I have now entirely
abandoned cofunctions/costates.

----------
> From: ian_d_chapman <idchapman@idch...>
> To: rabbit-semi@rabb...
> Subject: [rabbit-semi] Multitasking with Costates and Cofunctions in
Dynamic C
> Date: Friday, March 19, 2004 7:01 AM
>
> Hi!
>
> I'm new to this forum, and about to take my first hesitant steps
> with the Rabbit and Dynamic C. I have an RCM3600 development kit on
> order and should receive it next week. In the meantime, I have been
> reading the on-line documentation and trying to figure out how best
> to implement a multitasking design using Dynamic C.
>
> I'll try to explain my initial thoughts below. I'd greatly
> appreciate any steers on the best way to proceed and any potential
> pitfalls (especially undocumented ones!).
>
> My application needs to control two identical devices concurrently
> (each connected to its own serial port). For each device, there is
> an "initialisation" phase and a "run" phase. I'm thinking of using
> code along the following lines:
>
> main()
> {
> while (1)
> {
> costate // Device 0
> {
> wfd cof_dev_init(0);
> wfd cof_dev_run(0);
> }
> costate // Device 1
> {
> wfd cof_dev_init(1); // Re-use functions
> wfd cof_dev_run(1); // <-
> }
> // Other costates to be added
> }
> }
>
> cofunc void cof_dev_init(int i)
> {
> auto int a_var; // Separate instances?
> static int s_var[2]; // Must be indexed by i
>
> cof_send_string(i, "Hello!"); // Needs to yield or wait
> waitfor(DelayMs(500));
> flip_output(i); // Instant completion
> }
>
> // ... etc. ...
>
> At this stage, I have the following specific questions:
>
> 1. Will this approach work? In particular, is it okay to nest
> cofunctions in this way (i.e. cof_send_string() inside cof_dev_init
> () inside the costates for devices 0 and 1)? Is there a heavy
> penalty in terms of memory usage or performance?
>
> 2. Am I correct in assuming that cof_dev_init() and cof_dev_run()
> cannot be single-user cofunctions, because two instances of each
> need to run concurrently for devices 0 and 1?
>
> 3. Is it true that independent instances of the "a_var" will be
> automatically created for cof_dev_init(0) and cof_dev_init(1)? I
> assume that this isn't true for "s_var", though.
>
> 4. Would it be better to change cof_dev_init() and cof_dev_run()
> into indexed cofunctions (i.e. cof_dev_init[i]() and cof_dev_run[i]
> ())? If so, then:
>
> (a) How is the indexed cofunction declared? For example, is
> it "cofunc void cof_dev_init[int i](void)"?
>
> (b) Is the value of index i available inside the function so that it
> can be passed as a parameter to cof_send_string() and flip_output()?
>
> Many thanks in advance for any pointers.
>
> All the best.
> --
> Ian Chapman
> Chapmip Technology >
>
> Yahoo! Groups Links



Many thanks, Robert. That's a really comprehensive explanation. It
certainly helps me to have a better understanding of what's going
on "under the hood". I've read Section 5 of the Dynamic C User's
Manual several times and looked at some examples, but these
particular aspects didn't seem to be covered.

The burning question in my mind now is what the function definition
for an indexed cofunction looks like. I'll take a guess that my
example would become:

cofunc void cof_dev_init[2](int i)
{
auto int a_var; // Separate instances
static int s_var[2]; // Must be indexed by i

wfd cof_send_string[i](i, "Hello!"); // Needs to yield/wait
waitfor(DelayMs(500));
flip_output(i); // Instant completion
}

I'd then call this cofunction from the costates in main() with the
index specified twice (once as a function parameter which can be
passed on to functions which are called within the cofunction) - for
example:

costate // Device 0
{
wfd cof_dev_init[0](0);
wfd cof_dev_run[0](0);
}
costate // Device 1
{
wfd cof_dev_init[1](1);
wfd cof_dev_run[1](1);
}

This seems a bit messy: is there any way for the body of the
cofunction to discover which value of index (in square brackets) the
cofunction was called with? Just to explain why this is important:
although my "initialisation" and "run" logic is mostly the same for
both devices, it's necessary to select the correct serial port - for
example:

cofunc void cof_send_string[2](int i, char *string)
{
if (i == 0)
wfd cof_serCputs(string);
else
wfd cof_serDputs(string);
}

Hopefully you can see what I'm trying to achieve.

> Cofunctions are an invention of ZWorld to cooperative multitasking.
> Instead of stack juggling, which is the traditional method, they
> multitask without using the stack at all. Every cofunction's auto
> variables as well as static variables are given static storage, so
> there is no difference between the two if the cofunction isn't
> indexed. When yield is encountered, the calling costate or
> cofunction has static storage set aside for where the processor
> was; this is where it returns the next time that costate is
> encountered; do you see how this avoids multiple stacks?

Yes - got it! I'm sure I'll have more questions when I've thought
about this a bit harder, but for the moment you've definitely
pointed me in the right direction, apart from my burning question
above.

Many thanks once again.
--
Ian Chapman
Chapmip Technology


Dear Ian,

I know this does not really answer the question, but is there any
reason you don't use uCOS-II. It's a real operating system with
individual stacks for each task, which sounds like where part of your
problem lies. I found that having to deal with CoFunctions makes the
code too Rabbit-specific for my liking.

Good luck to you,

Alkina --- In rabbit-semi@rabb..., "ian_d_chapman" <idchapman@i...>
wrote:
> Hi!
>
> I'm new to this forum, and about to take my first hesitant steps
> with the Rabbit and Dynamic C. I have an RCM3600 development kit on
> order and should receive it next week. In the meantime, I have been
> reading the on-line documentation and trying to figure out how best
> to implement a multitasking design using Dynamic C.
>
> I'll try to explain my initial thoughts below. I'd greatly
> appreciate any steers on the best way to proceed and any potential
> pitfalls (especially undocumented ones!).
>
> My application needs to control two identical devices concurrently
> (each connected to its own serial port). For each device, there is
> an "initialisation" phase and a "run" phase. I'm thinking of using
> code along the following lines:
>
> main()
> {
> while (1)
> {
> costate // Device 0
> {
> wfd cof_dev_init(0);
> wfd cof_dev_run(0);
> }
> costate // Device 1
> {
> wfd cof_dev_init(1); // Re-use functions
> wfd cof_dev_run(1); // <-
> }
> // Other costates to be added
> }
> }
>
> cofunc void cof_dev_init(int i)
> {
> auto int a_var; // Separate instances?
> static int s_var[2]; // Must be indexed by i
>
> cof_send_string(i, "Hello!"); // Needs to yield or wait
> waitfor(DelayMs(500));
> flip_output(i); // Instant completion
> }
>
> // ... etc. ...
>
> At this stage, I have the following specific questions:
>
> 1. Will this approach work? In particular, is it okay to nest
> cofunctions in this way (i.e. cof_send_string() inside cof_dev_init
> () inside the costates for devices 0 and 1)? Is there a heavy
> penalty in terms of memory usage or performance?
>
> 2. Am I correct in assuming that cof_dev_init() and cof_dev_run()
> cannot be single-user cofunctions, because two instances of each
> need to run concurrently for devices 0 and 1?
>
> 3. Is it true that independent instances of the "a_var" will be
> automatically created for cof_dev_init(0) and cof_dev_init(1)? I
> assume that this isn't true for "s_var", though.
>
> 4. Would it be better to change cof_dev_init() and cof_dev_run()
> into indexed cofunctions (i.e. cof_dev_init[i]() and cof_dev_run[i]
> ())? If so, then:
>
> (a) How is the indexed cofunction declared? For example, is
> it "cofunc void cof_dev_init[int i](void)"?
>
> (b) Is the value of index i available inside the function so that it
> can be passed as a parameter to cof_send_string() and flip_output()?
>
> Many thanks in advance for any pointers.
>
> All the best.
> --
> Ian Chapman
> Chapmip Technology




"alkina_eq" wrote:
> I know this does not really answer the question, but is there any
> reason you don't use uCOS-II. It's a real operating system with
> individual stacks for each task, which sounds like where part of
> your problem lies. I found that having to deal with CoFunctions
> makes the code too Rabbit-specific for my liking.

That's an option, but I don't think it will be too difficult to make
this work under Dynamic C using cofunctions once I understand enough
about how they work. I'll base my application on some existing C
code which uses explicit state machines (switch / case statements)
to control the devices and which runs fine on a smaller micro. I
was hoping that the intrinsic support for multitasking in Dynamic C
would simplify the code and make it easier to add additional states,
but if it poses too many problems then I could just stick to the
original switch / case approach.

Having said all that, I can see that uCOS-II is a good direction to
take if things get much more complicated. Thanks for mentioning it -
I'll certainly bear it in mind.

All the best
--
Ian Chapman
Chapmip Technology, UK



The 2024 Embedded Online Conference