|
@@ -5,7 +5,7 @@ email: <patb@pbeirne.com>
|
|
|
date: 2025/01/15
|
|
|
license: MIT
|
|
|
---
|
|
|
-<style> h2 { border-top: solid thin #899;} </style>
|
|
|
+<style> h2 { border-top: solid thin #899;} summary {color: #08b;} </style>
|
|
|
# Part 1: Cooperative Multitasking
|
|
|
|
|
|
<!-- ## Introduction -->
|
|
@@ -48,26 +48,28 @@ It helps to have a passing knowledge of how to connect a transducer
|
|
|
<details><summary>Reality Check</summary>
|
|
|
The technique described here is also called ***event driven programming***.
|
|
|
|
|
|
-This technique was used in the original Window (1995),
|
|
|
-including some of the system calls: `sendMessage(), postMessage() and setTimer()`.
|
|
|
+This technique was used in the original Windows (1995),
|
|
|
+and includes some of the system calls used in this paper:
|
|
|
+`sendMessage(), postMessage() and setTimer()`.
|
|
|
|
|
|
-This event-driven technique is applicable to a whole host
|
|
|
+This event-driven technique is applicable to a whole array
|
|
|
of small microcontrollers, including
|
|
|
|
|
|
- MSP430
|
|
|
-- Cortex M0, M0+ (SAM, STM32, PY32, Cypress, Kinetis, HT32, XMC, LPC81x)
|
|
|
-- AtMega, AtTiny
|
|
|
+- Cortex M0, M0+ (SAM, STM32, PY32, Cypress, Kinetis, HT32, XMC, LPC81x, CH32V)
|
|
|
+- AtMega, AtTiny, LGT8F
|
|
|
- 8051 (SiliconLabs, Nuvoton, HT85)
|
|
|
- RL78 (Renesas)
|
|
|
- Pic 12/14/16
|
|
|
- Risc (ch32v)
|
|
|
- STM8
|
|
|
|
|
|
-*Some of the really tiny ones don't have enough stack space to implement these
|
|
|
-techniques (Puolop PB150, Padauk PxS15x, Bojuxing BJ8P,
|
|
|
+*Some of the really tiny ones probably don't have enough stack space to implement the
|
|
|
+technique described in this paper (Puolop PB150, Padauk PxS15x, Bojuxing BJ8P,
|
|
|
Yspring MDT1x, EastSoft HR7P, Holtek Ht68.)*
|
|
|
|
|
|
- is a typical board hosting 12 buttons, 12 RGB LEDs, all hosted
|
|
|
+ This is a typical board hosting 12 buttons,
|
|
|
+12 RGB LEDs, all hosted
|
|
|
by an STM32. Every light and button can be controlled separately
|
|
|
and simultaneously, using the technique described in this paper.
|
|
|
|
|
@@ -117,9 +119,9 @@ Of course, we will need to write the code for `setup_hardware()`
|
|
|
and `setup_interrupts()`.
|
|
|
And `flashLedTask()` and `respondToButtonTask()`. And create the *magic*
|
|
|
that allows `event` information to flow.
|
|
|
-Don't worry, it will all be laid out in the following pages.
|
|
|
+All this will be laid out in the following pages.
|
|
|
|
|
|
-If you're concerned with complexity, feel free to jump ahead to the
|
|
|
+If you're concerned with complexity, feel free to jump ahead about 10 pages to the
|
|
|
[final implementation](#final-implementation) page to see real, tested, code.
|
|
|
|
|
|
Between here and there, I'll walk you step-by-step through building
|
|
@@ -145,7 +147,7 @@ Each interrupt causes the execution of *interrupt handler* code. For this projec
|
|
|
something like this:
|
|
|
|
|
|
```C
|
|
|
-enum {EVT_NONE, EVT_TICK, EVT_BUTTON};
|
|
|
+enum {EVT_NONE=0, EVT_TICK, EVT_BUTTON};
|
|
|
|
|
|
INTERRUPT timer_isr(void) {
|
|
|
newEvent(EVT_TICK);
|
|
@@ -171,6 +173,7 @@ cases, there's no need to ack. Specifically,
|
|
|
the SYS_TICK interrupt in the ARM Cortex processors does *not*
|
|
|
need an ack, so the above code example for `timer_isr()` is complete.
|
|
|
|
|
|
+<hr>
|
|
|
When a pushbutton or switch is connected to a microcontroller,
|
|
|
the first bit of activity will
|
|
|
cause the interrupt and execute the `button_isr()` code.
|
|
@@ -178,11 +181,12 @@ However, real buttons produce about 5msec of
|
|
|
*bounce* and this will cause subsequent interrupts unless
|
|
|
they are somehow filtered out. There are
|
|
|
lots of ways to handle *bounce*, and I'll let you read about that
|
|
|
-[elsewhere]](#debouncing). Most techniques boil
|
|
|
+[elsewhere](#debouncing). Most techniques boil
|
|
|
down to either ignoring subsequent interrupts (from the same button)
|
|
|
for about 5 msec, or disabling
|
|
|
that specific interrupt until the 5msec has passed.
|
|
|
|
|
|
+<hr>
|
|
|
As a general rule, *input* pins should be observed either by
|
|
|
interrupt service routine (ISR), or scanned
|
|
|
periodically by the timer ISR. *Outputs* should be
|
|
@@ -195,7 +199,7 @@ controlled in the task code, which we'll see below.
|
|
|
An *event* in this context is a small bit of information that appears
|
|
|
asynchronously in the system.
|
|
|
Implemented, it can be a *byte*, or an *int* or an even larger structure.
|
|
|
-But in these small microcontrollers, let's use a *byte*.
|
|
|
+But in these small microcontrollers, let's use a byte.
|
|
|
|
|
|
```C
|
|
|
volatile uchar event;
|
|
@@ -206,11 +210,10 @@ and processed at the main level. We use the `event` object to send
|
|
|
information between these levels, so we have to mark it
|
|
|
[`volatile`](https://en.wikipedia.org/wiki/Volatile_(computer_programming)).
|
|
|
|
|
|
-In this paper, the word *message* and *event* are equivalent.
|
|
|
-*Message* has the sense of
|
|
|
+In this paper, the words *event* and *message* are equivalent.
|
|
|
+*Event* has the sense of "something external just happened" and
|
|
|
+*message* has the sense of
|
|
|
"a bit of communication from another part of the program"
|
|
|
-and *event* has the sense of "something
|
|
|
-external just happened".
|
|
|
At this point in the design, they both mean the same thing.
|
|
|
|
|
|
By convention, let's use *zero* to indicate the absence of an event/message,
|
|
@@ -219,14 +222,14 @@ an event/message.
|
|
|
|
|
|
```C
|
|
|
volatile uchar event;
|
|
|
-enum {EVT_NONE, EVT_TICK, EVT_BUTTON}; // 0,1,2
|
|
|
+enum {EVT_NONE=0, EVT_TICK, EVT_BUTTON}; // 0,1,2
|
|
|
```
|
|
|
|
|
|
<details>
|
|
|
<summary>Reality check</summary>
|
|
|
|
|
|
<hr>
|
|
|
-For this project, let's suppose the timer ticks happen every 10ms (100 per second).
|
|
|
+For this example project, let's suppose the timer ticks happen every 10ms (100 per second).
|
|
|
<hr>
|
|
|
|
|
|
</details>
|
|
@@ -283,12 +286,12 @@ Here's a more realistic version:
|
|
|
|
|
|
```C
|
|
|
void newEvent(char evt) {
|
|
|
- static uchar nextEvent; // keep track of where we are in queue
|
|
|
+ static uchar nextEvent; // keep track of where we are in queue
|
|
|
disable_irq(); // critical section
|
|
|
events[nextEvent++] = evt; // insert event into queue
|
|
|
if (nextEvent == NUM_EVENTS) // loop back to the start of queue
|
|
|
nextEvent = 0;
|
|
|
- enable_irq(); // end critical section, probably <100us of blockage
|
|
|
+ enable_irq(); // end critical section, probably <100us of blockage
|
|
|
}
|
|
|
```
|
|
|
</details>
|
|
@@ -312,11 +315,7 @@ To see the real code for the dispatcher, [jump ahead](#dispatcher-details).
|
|
|
The `main()` code illustrated here calls the tasks
|
|
|
as subroutines, sending each one a copy of the event number.
|
|
|
|
|
|
-As I mentioned earlier, *events* can also be referred to as *messages*.
|
|
|
-We can also refer to this process "sending
|
|
|
-a message to the task"
|
|
|
-
|
|
|
-So, for this paper, these are equivalent
|
|
|
+For this paper, these are equivalent
|
|
|
|
|
|
- calling a task subroutine with the event information
|
|
|
- sending a message to a task
|
|
@@ -372,7 +371,7 @@ state, which can be represented in a net diagram.
|
|
|
Here's a state diagram for the task which reacts to a button press by
|
|
|
flashing an LED.
|
|
|
|
|
|
-
|
|
|
+
|
|
|
|
|
|
How does the *state code* remember what state it's in between invocations?
|
|
|
We can use a `static` variable. A `static` is stored in main memory (*not
|
|
@@ -407,7 +406,8 @@ void respondToButtonTask(uchar evt) {
|
|
|
}
|
|
|
```
|
|
|
|
|
|
-Each time this routine is called, it checks the *event* that it was given,
|
|
|
+Each time this routine is called, it first forks, depending on the current state.
|
|
|
+Then it checks the *event* that it was given,
|
|
|
and sometimes processes it. And sometimes it changes state.
|
|
|
|
|
|
Here's a few things to notice:
|
|
@@ -694,7 +694,7 @@ What's missing from this code sample is the initialization of the
|
|
|
hardware........some kind of `setup()` routine. I left it out because
|
|
|
it's going to vary so much between processors, and only distracts from the
|
|
|
point of this paper. If you want a detailed, tested copy of this code,
|
|
|
-see [here](build/demo.c)
|
|
|
+see it [here](#real-code) or download it [here](build/demo.c)
|
|
|
|
|
|
The above example only shows 2 tasks, so all the formal structure may
|
|
|
seem a bit silly. But once you have the infrastructure in place, you can
|
|
@@ -889,7 +889,6 @@ handle buffering the events until they can be processed.
|
|
|
|
|
|
Under no circumstances should a task take more than 100msec. Use a new state,
|
|
|
and return from the task. Process the new state later.
|
|
|
-<hr>
|
|
|
</details>
|
|
|
|
|
|
|
|
@@ -1870,3 +1869,31 @@ void checkRadioFreq() {
|
|
|
This may seem like a lot of code, but it compiles to something quite small.
|
|
|
It is non-blocking, and easily maintained and modified. And it works.
|
|
|
|
|
|
+### Temperature Control
|
|
|
+
|
|
|
+Another example would be a user interface that had two buttons,
|
|
|
+marked "up" and "down", an LED marked "setting",
|
|
|
+some sort of numeric display, and some output
|
|
|
+device which needs to be controlled, such as a heater element, a motor on
|
|
|
+a mixer or even a volume setting on a sound system.
|
|
|
+
|
|
|
+The user presses the "up" or "down" button, and the "setting: LED lights,
|
|
|
+to inform the user. Further presses of "up" or "down" modify the
|
|
|
+numeric display, but don't yet affect the device which is being controlled.
|
|
|
+
|
|
|
+When the users pauses for 1.5 seconds, the LED is extinguished and
|
|
|
+the new setting is used to control the output device.
|
|
|
+
|
|
|
+The state diagram might look something like
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+## UART Manager
|
|
|
+
|
|
|
+It's pretty common for these small microprocessors to have a UART service
|
|
|
+builtin. And it's also pretty common for these small micros to be used as
|
|
|
+a slave to a more sophisticated master computer.
|
|
|
+
|
|
|
+So I have written a [UART streaming service as a task](uart.html)
|
|
|
+which fits into
|
|
|
+this *Cooperative Multitasking* (*event driven*) operating system.
|