Example (Advanced): Using semaphores to latch inputs
In some applications there is an inherent hazard in reading an input several times in succession, because of the danger of the input changing between reads. Suppose for example we have an application which must decide to either fill or drain depending on the following conditions (X means “don’t care”)
| Input A | Input B | Input C | Action |
|---|---|---|---|
| ON | ON | X | Fill |
| OFF | OFF | ON | Drain |
We might write the following code:
QryFill: Input A ;Read A
Input B ;Read B
AND ;AND them together
GoIfT Fill ;Test result, Fill if true
QryDrain: Input A ;Read A
NOT ;Want A off, so invert
Input B ;Read B
NOT ;Want B off, so invert
AND ; A' and B'
Input C ;Read C
AND ; A' AND B' AND C
GoIfT Drain ;Test result, drain if true
This will work correctly … 99.9% of the time. However, if B and C are on, if A should be off the first time it is read, and then manage to turn on before the second read (5th line), then the program will neither fill nor drain. This is the kind of coding ‘gotcha’ that leads to pesky, virtually uncatchable bugs.
Now, you may well spot some way of fixing this up without using semaphores. In fact, consider that as an exercise! However, the following use of a semaphore would also work:
Input A ;Read A
StoreS SaveA ;Save it in a semaphore
Input B ;Read B
StoreS SaveB ;Save it in a semaphore
RecallS SaveA ;Use the latched version of input A
RecallS SaveB ;Use the latched version of input BAND ;AND them togetherGoIfT Fill ;Test result, Fill if true
RecallS SaveA ;Use the latched version of input ANOT ;Want A off, so invert
RecallS SaveB ;Use the latched version of input BNOT ;Want B off, so invertAND ; A' and B'Input C ;Read CAND ; A' AND B' AND C
GoIfT Drain ;Test result, drain if true
In this code we only read A once, then save it in a semaphore. After that it doesn’t matter if the actual input changes, because we will be using the one latched sample in our decisions.
A technique that you can use if your program is structured as one great big loop that executes repeatedly (see the multitasking tutorial), is to sample all inputs at once at the top of the program loop, something like this:
MainLoop: InputFM 0 ;get inputs 0-7
Store InputSems0 ;Store in the 1st input semaphore byte
InputFM 8 ;get inputs 8-15
Store InputSems8 ;Store in the 2nd input semaphore byte
InputFM 16 ;get inputs 16-23
Store InputSems16 ;Store in the 3rd input semaphore byte
; ... etc ...
RunTasks
GoTo MainLoop
Within each task any reference to inputs would actually access the stored values via semaphore instructions:
Task_1: GoIfST Sensor,InputSems0,GotSensor
; ... etc
What is happening here?
The first InputFM instruction reads the states of inputs 0 through 7 as a single byte of data, one bit per input, into the X register. The Store InputSems0 then saves that byte (8 bits/ 8 inputs) in RAM location InputSems0 (which you must define in an mEQU). Later on, in line Task_1 we test a semaphore Sensor counted from base address InputSems0. If say Sensor is sEQUated to 3, what we are actually testing is the (semaphore representing the) state of input 3 as it was when we sampled it at MainLoop.
The above explanation is for inputs 0-7. Inputs 8-15 are read with an InputFM 8 and saved in semaphore byte InputSems8. When you subsequently access the semaphores you have a choice of what base address you use in the semaphore instruction. If you use InputSems8 as the base address, you will have to use semaphore numbers 0-7. If you use InputSems0 as the base address, you will have to use semaphores 8-15, providing InputsSems8 follows immediately after InputSems0 in RAM. If this statement is not clear to you, go back and re-study the description of semaphore addressing.