Explorar el Código

changed uint8 to uchar (it looks nicer) and cleaned up postMessage/sendMessage

Pat Beirne hace 1 mes
padre
commit
f3ad6ca5d3
Se han modificado 4 ficheros con 78 adiciones y 74 borrados
  1. 77 74
      CooperativeMultitasking.md
  2. 1 0
      c.xml
  3. BIN
      postMessage.png
  4. BIN
      sendMessage.png

+ 77 - 74
CooperativeMultitasking.md

@@ -5,7 +5,7 @@ email: <patb@pbeirne.com>
 date: 2025/01/15
 license: MIT
 ---
-
+<style> h2 { border-top: solid thin #899;}   </style>
 # Part 1: Cooperative Multitasking
 
 <!-- ## Introduction -->
@@ -22,7 +22,7 @@ The core of *this* technique is covered in
 [Part 1](#part-1-cooperative-multitasking)
 of this paper. 
 If you just want to see how it all comes together, 
-jump to [Final Implementaion](#final-implementation). 
+jump to [Final Implementation](#final-implementation). 
 
 [Part 2](#part-2) contains enhancements and variations, 
 probably useful reading if you decide to adopt
@@ -86,7 +86,7 @@ to make this happen?
 
 ## First Page
 
-Let's look an a pseudocode overview of what we want to do:
+Let's look at the pseudocode overview of what we want to do:
 
 ```C
 void initialize(void) {
@@ -137,12 +137,12 @@ In order to have a responsive system, it would make sense to use the
 
 In the task described, we need to respond to a button press. 
 So let's connect the button to 
-an input pin and enable it to repsond to a button press with interrupt code.
+an input pin and enable it to respond to a button press with interrupt code.
 
 We also need to keep track of time.....so let's hook up a system timer to another interrupt.
 
 Each interrupt causes the execution of *interrupt handler* code. For this project, it might look
-somthing like this:
+something like this:
 
 ```C
 enum {EVT_NONE, EVT_TICK, EVT_BUTTON};
@@ -198,7 +198,7 @@ Implemented, it can be a *byte*, or an *int* or an even larger structure.
 But in these small microcontrollers, let's use a *byte*.
 
 ```C
-volatile uint8 event;
+volatile uchar event;
 ```
 
 These *events* will be created in interrupt level of the code, 
@@ -218,7 +218,7 @@ and *non-zero* to represent
 an event/message.
 
 ```C
-volatile uint8 event;
+volatile uchar event;
 enum {EVT_NONE, EVT_TICK, EVT_BUTTON}; // 0,1,2
 ```
 
@@ -244,13 +244,13 @@ How do we send the information (events/messages) from the interrupt
 service routine to the 
 `main()` code? We used shared memory.
 
-One way would be to have a global `volatile uint8` location into which 
+One way would be to have a global `volatile uchar` location into which 
 we drop the *event* information. But
 having only one socket for that would be a bit naive; 
 what happens if a timer tick and a button press
 happen very close in time? What happens if the timer tick events start to stack up?
 
-It makes more sense to have an array: `volatile uint8 events[NUM_EVENTS]` 
+It makes more sense to have an array: `volatile uchar events[NUM_EVENTS]` 
 where NUM_EVENTS is on the order of 5..10. 
 That would give us 50-100msec to catch up in case there's a 
 pileup of events/messages.
@@ -263,8 +263,8 @@ The `newEvent(evt)` routine simply adds the `evt` to the array `events[]`.
 Something like this might work:
 
 ```C
-void newEvent(uint8 evt) {
-  static uint8 nextEvent;
+void newEvent(uchar evt) {
+  static uchar nextEvent;
   events[nextEvent++] = evt;
   if (nextEvent == NUM_EVENTS)
     nextEvent = 0;
@@ -283,7 +283,7 @@ Here's a more realistic version:
 
 ```C
 void newEvent(char evt) {
-  static uint8 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
@@ -312,7 +312,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 refered to as *messages*. 
+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"
 
@@ -345,11 +345,11 @@ the event, speeding up the whole system.
 
 ## Tasks
 
-In this environment, the code that impliments a *task* is simply a 
+In this environment, the code that implements a *task* is simply a 
 subroutine that accepts an *event/message*.
 
 ```C
-void taskCode(uint8 event) {
+void taskCode(uchar event) {
   ... process the event information ...
 }
 ```
@@ -382,11 +382,11 @@ The above diagram can be implemented in this code:
 
 ```C
 enum {RTB_IDLE, RTB_ON};          // RTB_IDLE=0, RTB_ON=1
-static uint8  rtbState      = RTB_IDLE;
+static uchar  rtbState      = RTB_IDLE;
 static uint16 rtbTimerCount = 0;
 const  uint16 TIMER_LIMIT   = 150;
 
-void respondToButtonTask(uint8 evt) {
+void respondToButtonTask(uchar evt) {
   switch(rtbState) {
     case RTB_IDLE:
       if (evt == EVT_BUTTON) {
@@ -440,12 +440,12 @@ The code for this might be:
 
 ```C
 enum {LED_ON, LED_OFF};
-static uint8 ledState       = LED_OFF;
+static uchar ledState       = LED_OFF;
 static uint16 ledTimerCount = 0;
 const  uint16 LED_ON_TIME   = 100;
 const  uint16 LED_OFF_TIME  = 100;
 
-void ledTask(uint8 evt) {
+void ledTask(uchar evt) {
   switch(ledState) {
     case LED_OFF:
       if (evt == EVT_TICK) {
@@ -569,11 +569,11 @@ Let's put all of the above together, for an SMT32F Cortex M0, in *C* code.
 ```C
 /***** declarations ****/
 #define NUM_EVENTS 10
-volatile uint8 events[NUM_EVENTS];
+volatile uchar events[NUM_EVENTS];
 
-void newEvent(uint8 e);
-void ledTask(uint8 evt);
-void respondToButtonTask(uint8 evt);
+void newEvent(uchar e);
+void ledTask(uchar evt);
+void respondToButtonTask(uchar evt);
 
 /********** interrupts **************/
 
@@ -593,8 +593,8 @@ void button_isr(void) {
  * 
  * @param the event
  */
-void newEvent(uint8 e) {
-  static uint8 nextEvent;
+void newEvent(uchar e) {
+  static uchar nextEvent;
   dint(); // critical section
   events[nextEvent++] = e;
   if (nextEvent==NUM_EVENTS)
@@ -623,11 +623,11 @@ void main(void) {
 /*********** task code, with states ************/
 enum {LED_ON, LED_OFF};
 enum {RTB_IDLE, RTB_ON};    // states
-static uint8  rtbState      = RTB_IDLE;
+static uchar  rtbState      = RTB_IDLE;
 static uint16 rtbTimerCount = 0;
 const  uint16 LED_ON_TIME   = 150;
 
-void respondToButtonTask(uint8 evt) {
+void respondToButtonTask(uchar evt) {
   switch(rtbState) {
     case RTB_IDLE:
       if (evt == EVT_BUTTON) {
@@ -649,10 +649,10 @@ void respondToButtonTask(uint8 evt) {
 
 const  uint16 LED_ON_TIME   = 150;
 const  uint16 LED_OFF_TIME  = 50;
-static uint8 ledState       = LED_OFF;
+static uchar ledState       = LED_OFF;
 static uint16 ledTimerCount = 0;
 
-void ledTask(uint8 evt) {
+void ledTask(uchar evt) {
   switch(ledState) {
     case LED_OFF:
       if (evt == EVT_TICK) {
@@ -714,7 +714,7 @@ LED timer on each key press:
 <div style="display: flex;">
 ![RTB (respond to button) state machine, with restart](rtb_state_2.png)\ 
 ```C
-void rtbTask(uint8 event) {
+void rtbTask(uchar event) {
   switch(rtbState) {
     case RTB_IDLE:
       if (event == EVT_BUTTON) {
@@ -746,7 +746,7 @@ Or have a 2nd press of the button cause the LED to extinguish early:
 <div style="display: flex;">
 ![RTB state machine, with early-off](rtb_state_3.png)\ 
 ```C
-void rtbTask(uint8 event) {
+void rtbTask(uchar event) {
   switch(rtbState) {
     case RTB_IDLE:
       if (event == EVT_BUTTON) {
@@ -783,7 +783,7 @@ it: (see also [substates](#substates))
 ![RTB state machine, push to stop](rtb_state_4.png)\ 
 
 ```C
-void rtbTask(uint8 event) {
+void rtbTask(uchar event) {
   switch(rtbState) {
     case RTB_IDLE:
       if (event == EVT_BUTTON) {
@@ -888,7 +888,7 @@ If a task occasionally runs into the 10's of msec, the event queue will
 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 tast. Process the new state later.
+and return from the task. Process the new state later.
 <hr>
 </details>
 
@@ -915,9 +915,9 @@ and the code: arrows leaving a state correspond to an `if()` phrase.
 
 ```C
 enum {WINDOW_IDLE, WINDOW_UP, WINDOW_DOWN};
-static uint8 windowState = WINDOW_IDLE;
+static uchar windowState = WINDOW_IDLE;
 
-void windowTask(uint8 evt) {
+void windowTask(uchar evt) {
   switch(windowState) {
     case WINDOW_IDLE:
       if (evt == EVT_BUTTON_UP) {
@@ -962,7 +962,7 @@ code from the previous version.
 <details><summary>Reality Check</summary>
 This is such a simple task, with only a few I/O pins involved. In theory, a
 cheap microcontroller could control a dozen windows, each appearing to operate
-independantly. 
+independently. 
 
 In the code, one wouldn't need to create a dozen tasks......just create an index
 into the same code and invoke it in a way that makes it appear as an 
@@ -1020,7 +1020,7 @@ And here is the corresponding code.
 
 ```C
 enum {FRIDGE_CLOSED, FRIDGE_OPEN, FRIDGE_BEEPING};
-uint8  fridgeState;
+uchar  fridgeState;
 uint16 fridgeTimer;
 const uint16 FRIDGE_OPEN_LIMIT = 9000; // 90 seconds at 10msec tick
 void fridgeTask(char event) {
@@ -1135,18 +1135,18 @@ Here's the state diagram:
 
 ![RFID operated door lock](door.png)
 
-I use `EVT_` type events to indicate that they originate in hardware, probably
-at the interrupt level; and `MSG_` type events to indicate they come from a
-software source, perhaps a sibling task.
-
 Suppose now that the serial number needs to be verified by a central service.
 So when an RFID tag is detected, send a request to the master control and 
 wait for an ACK or NAK response. In the case of an ACK, open the door solenoid
 for 4 seconds. The rest of the problem is as stated above.
 
+I use `EVT_` type events to indicate that they originate in hardware, probably
+at the interrupt level; and `MSG_` type events to indicate they come from a
+software source, perhaps a sibling task.
+
 Here's the modified state diagram:
 
-![RFID operated door, with remove key verify](door2.png)
+![RFID operated door, with remote key verify](door2.png)
 
 ### Beer Vat
 
@@ -1198,16 +1198,17 @@ Event e = {MSG_KEYPRESS, KEY_A};
 ```
 
 In the old Windows system, *events/messages* were realized as a 16 bit number,
-with an extra 32 bit number glued to it for extra information. 
+with extra 16 and 32 bit numbers glued to it for extra information. 
 
 ```C
 typedef struct MSG {
   UINT   message;
+  WPARAM wParam;
   LPARAM lParam;
 };    // some extra detail removed
 ```
 For example, 
-`WM_CHAR=0x0102` indicates that a key was pressed, with the extra 32bit `lParam`
+`message == WM_CHAR == 0x0102` indicates that a key was pressed, with the `wParam`
 carrying the information about *which* key. 
 <hr>
 
@@ -1253,8 +1254,8 @@ and increment until it hits the desired limit (100 in this case).
 In a moderate sized project, timers like this will proliferate throughout the code,
 making it awkward to read. One solution to this is to centralize the timers.
 
-In all the above examples, the `timer_irq()` code is trivial, just `newEvent(EVT_TICK)`.
-Suppose we add code to the `timer_irq()` so that it can process timer counting on
+In all the above examples, the `timer_isr()` code is trivial, just `newEvent(EVT_TICK)`.
+Suppose we add code to the `timer_isr()` so that it can process timer counting on
 behalf of the tasks......
 
 ### Timers as a Resource
@@ -1262,9 +1263,9 @@ behalf of the tasks......
 Let's create a centralized service called `setTimer(timer_index, timer_count)`. 
 
 A task can call this service with a unique `timer_index` and a requested count. The
-`timer_irq()` uses a pool of timer registers, and will count out the ticks 
+`timer_isr()` uses a pool of timer registers, and will count out the ticks 
 on behalf of the task, and when the tick
-count is finished, the `timer_irq()` code can generate a unique event, perhaps
+count is finished, the `timer_isr()` code can generate a unique event, perhaps
 `EVT_TIMER_n`. 
 
 So the state code can then look something like this:
@@ -1294,7 +1295,7 @@ void stateCode(char event) {
 This makes the state code simpler to read, 
 hiding all the increments/decrements and limit testing.
 
-The overhead for this ends up in the `timer_irq()` code, 
+The overhead for this ends up in the `timer_isr()` code, 
 and might look something like this:
 
 ```C
@@ -1303,7 +1304,7 @@ static uint16 timers[NUM_TIMERS];
 enum {EVENT_TIMER_1=EVT_TIMER_OFFSET, EVENT_TIMER_2, EVENT_TIMER_3}; // 100, 101...
 enum {TIMER_1, TIMER_2, TIMER_3};   // 0,1,2,....
 
-void timer_irq() {
+void timer_isr() {
   newEvent(EVT_TICK);           // the main tick, fires every time
   for (i=0; i<NUM_TIMERS) {     // the system timers, fires on completion
     if (timers[i]>0) {
@@ -1321,7 +1322,7 @@ void setTimer(int timerIndex, unsigned int timerCount) {
 
 <details><summary>Reality Check</summary>
 On a typical microcontroller running at 24MHz, with 5 timers, this adds about
-2 microseconds of extra time to the `timer_irq()` code, which typically runs every 
+2 microseconds of extra time to the `timer_isr()` code, which typically runs every 
 10 or 100msec. It simplifies the task code, makes it more legible
 and probably reduces bugs that may appear by duplication of code.
 
@@ -1339,7 +1340,7 @@ INTERRUPT timer_isr(void) {
 
 // .... state code ...
 static int targetTime;
-void stateCode(uint8 event) {
+void stateCode(uchar event) {
   switch(state) {
     case STATE1:
       //..... set a target
@@ -1399,15 +1400,15 @@ void gpio_set(uint32 bitPosition, bool value) {
 ```C
 /***** events ****/
 #define NUM_EVENTS 10
-volatile uint8 events[NUM_EVENTS];
+volatile uchar events[NUM_EVENTS];
 enum { EVT_NONE,
       EVT_TICK,
       EVT_BUTTON};
-void newEvent(uint8 e);
+void newEvent(uchar e);
 
 /********** tasks ***********/
-void ledTask(uint8 evt);
-void respondToButtonTask(uint8 evt);
+void ledTask(uchar evt);
+void respondToButtonTask(uchar evt);
 
 /********** interrupts **************/
 volatile uint32 tick;   // increasing at 100 ticks/sec
@@ -1503,11 +1504,11 @@ void main(void) {
 /*********** task code, with states ************/
 enum {LED_ON, LED_OFF};
 enum {RTB_IDLE, RTB_ON};    // states
-static uint8 rtbState            = RTB_IDLE;
+static uchar rtbState            = RTB_IDLE;
 static uint16 rtbTimerCount      = 0;
 const  uint16 BUTTON_LED_ON_TIME = 150;
 
-void respondToButtonTask(uint8 evt) {
+void respondToButtonTask(uchar evt) {
   switch(rtbState) {
     case RTB_IDLE:
       if (evt == EVT_BUTTON) {
@@ -1529,10 +1530,10 @@ void respondToButtonTask(uint8 evt) {
 
 const int LED_ON_TIME  = 150;
 const int LED_OFF_TIME = 50;
-static uint8  ledState      = LED_OFF;
+static uchar  ledState      = LED_OFF;
 static uint16 ledTimerCount = 0;
 
-void ledTask(uint8 evt) {
+void ledTask(uchar evt) {
   switch(ledState) {
     case LED_OFF:
       if (evt == EVT_TICK) {
@@ -1606,8 +1607,8 @@ simplifies:
 
 
 ```C
-uint8 rtbState = RTB_IDLE;              // major state, RTB_IDLE/RTB_FLASHING
-uint8 rtbSubState = RTB_FLASH_OFF;      // minor state, toggles LED on/off
+uchar rtbState = RTB_IDLE;              // major state, RTB_IDLE/RTB_FLASHING
+uchar rtbSubState = RTB_FLASH_OFF;      // minor state, toggles LED on/off
 ```
 
 Alternatively, you could use the *timer counter*
@@ -1620,7 +1621,7 @@ variable, and make changes at the half-way point through the count.
 ```C
 const uint16 FLASH_CYCLE_TIME 150;
 const uint16 FLASH_ON_TIME 40;
-uint8 rtbState = STATE_IDLE;
+uchar rtbState = STATE_IDLE;
 void rtbTaskCode(char event) {
   static uint16 flashCount=0;
 
@@ -1677,8 +1678,8 @@ Then, in the state code, you can catch that event and set up whatever might
 be required
 
 ```C
-uint8 myState;
-void stateCode(uint8 event) {
+uchar myState;
+void stateCode(uchar event) {
   if (event==EVT_INIT) {
     // ... do setup code
     myState = FIRST_STATE;
@@ -1707,7 +1708,7 @@ only be called from interrupts.
 ![cd-eject task uses postMessage() to broadcast a message](postMessage.png)
 
 ```C
-void postMessage(uint8 message);
+void postMessage(uchar message);
 ```
 
 Notice that `postMessage` can't return any information, because it's not 
@@ -1726,7 +1727,8 @@ In some cases we may want the sibling task to process the information immediatel
 This means:
 
 - we can be assured that the sibling task has fully processed the information
-- does not involve the dispatcher; but does require extra room on the stack
+- does not involve the dispatcher
+- requires extra room on the stack, since we're calling within a call
 - we *could* get a return value from the sibling task; since it's implemented as
 a subroutine, it's allowed to return a value
 
@@ -1735,15 +1737,15 @@ a subroutine, it's allowed to return a value
 To return a value means that the prototype for task functions would change from 
 
 ```C
-void taskCode(uint8 event);
+void taskCode(uchar event);
 to
-uint8 taskCode(uint8 event);
+uchar taskCode(uchar event);
 ```
 
 The service routine to send a message like this would look like:
 
 ```C
-uint8 SendMessage(int8 taskPointer(uint8), uint8 message) {
+uchar SendMessage(int8 taskPointer(uchar), uchar message) {
   return taskPointer(message);
 }
 ```
@@ -1752,7 +1754,8 @@ uint8 SendMessage(int8 taskPointer(uint8), uint8 message) {
 
 Why might one want to *send a message* between tasks?
 
-Suppose you have a rotary encoder, which sends quadrant signals, which need
+**PostMessage()** Suppose you have a rotary encoder, which sends 
+quadrant signals, which need
 to be interpreted as *clockwise* and *counter clockwise*. You could have one
 task devoted to determining the direction of the knob 
 (and [debouncing](#debouncing)), and have it *send* (or *post*) clean 
@@ -1763,7 +1766,7 @@ For example, a device which has
 a temperature set by "up/down" buttons on a front panel could receive the same
 controls from an infra-red remote, or even a serial port (perhaps for testing).
 
-And why `sendMessage()`? Perhaps you need to have a sibling task change
+**SendMessage()** Perhaps you need to have a sibling task change
 states, or process information *before* you continue in your task. Imagine
 there is a slave WiFi chip that needs to be powered up *before* you send it
 a request....you could use `sendMessage()` to activate the power, and then
@@ -1819,8 +1822,8 @@ uint16 radioTimer = 0;
 const uint16 radioTimeout = 5;  // 0.5 seconds
 
 enum {IDLE, FREQ_UP, FREQ_UP_FAST, FREQ_DN, FREQ_DN_FAST};
-uint8 state = IDLE;
-void radioTask(uint8 event) {
+uchar state = IDLE;
+void radioTask(uchar event) {
   switch(state) {
     case IDLE:
       if (event == EVT_UP_PRESS) {

+ 1 - 0
c.xml

@@ -86,6 +86,7 @@
       <item>char</item>
       <item>char16_t</item>
       <item>char32_t</item>
+      <item>uchar</item>
       <item>double</item>
       <item>float</item>
       <item>int</item>

BIN
postMessage.png


BIN
sendMessage.png