PDA

View Full Version : Trapping a flag/state change (rapidly)



HankMcSpank
- 29th April 2011, 09:52
So, I'm learning about HPWM...and for this project just using a few LEDs to set some simple patterns running.

I'm using DT's interrupts to trap a button being pressed, in the interrupt routine I increment a 'pattern' variable for each button press. This bit all works fine, so no problems there.

So the interrupt returns to the main program loop ....but the variable (flag) change is not trapped until the program gets to the point where it tests the variable. Once it tests the variable, it jumps to a new loop (to change the LEDS sequence).

Ok, here's the problem....

For the sake of example, lets say Pattern 1 is a chaser (leds lighting up/extinguishing in sequence) & pattern is all LEDS on. What's happening here is that Pattern1 is running (leds sequencing),then the button gets pressed....but after the button is realeased it takes a finite time for the variable change to be picked up upon (trapped) in the main loop ...so the LEDs still continue to 'sequence' until the pattern change is trapped in the main loop - it then jumps to the new Pattern routine (all LEDS on) just fine. It's this lag between the button being pressed & the main loop picking up on it, that's causing me troubles...it's not a huge lag, but it is discernible & I'm sure there a slicker way

I'm conscious that I'm still very much in the n00b camp & there must be a better way of having a program act in a zippy way to variable/flag changes.

HenrikOlsson
- 29th April 2011, 10:29
Hi Hank,
I'm not sure I can give you a general way of doing it.

One (obvious but not elegant) way is to simply check the flag several times as you go thru the main loop.

Another way, assuming you're using some kind of counter/index variable to "step" thru the individual states of each pattern, is to also reset that counter in the ISR thus forcing the "cycle" to start over. If the first thing you do in the "cycle" is to check the "pattern variable" then you'll get an imidiate switch of patterns.

I'm sure there are several other ways of handling it.

/Henrik.

HankMcSpank
- 29th April 2011, 12:07
Hi Hank,
I'm not sure I can give you a general way of doing it.

One (obvious but not elegant) way is to simply check the flag several times as you go thru the main loop.


Hi Henrik,

That's the way I'm doing it now, but it eats up program space!

Previously, (to save repetition) I made the mistake of using a GOSUB to check the 'pattern' variable regularly throught all my program segments & if it had changed, to point it to a different part of the program ....whoah schoolboy error, my pic started reseting (becuase I wasn't returning cleanly out of the gosub).

I'm not sure I'm understanding your other recommendation.

For anyone not sure what my issue is, here's a high level flow of what's going on



pattern1:
HIGH LED1
PAUSE 100
LOW LED1
PAUSE 100
HIGH LED2
PAUSE 100
LOW LED2
PAUSE 100

rinse, repeat....

if pattern = 2 then goto pattern2
goto pattern1

pattern2:
flash some other LED pattern
if pattern = 1 then goto pattern1
goto pattern2

ISR:
If a Switch is pressed in increment pattern variable



you can clearly see that in pattern1, unless I check the variable called 'pattern' after every LED state change, that -especially with a few LEDS in play - it could be a while before the pattern variable change is picked up.

cncmachineguy
- 29th April 2011, 15:02
Hank, I think the hangup for you is how you generate the pattern.

If you use some counters to "decide" when and what led to light up, instead of hanging your program for .4 seconds, you will see the flag check much faster.

Maybe have your interrupt be a time base, like .1mS or something. Then while in the ISR, check for button press and inc your "led counters"

I know this is vague, but all I have time for right now.

HenrikOlsson
- 29th April 2011, 16:26
Hi,
There are many different ways of doing it and the "right" one depends on what you'll actually intend to do with it - in the end.... It's not uncommon to post something that does what's currently 'at hand' only to get something like OK, that works, now I want it to do this and that which, if it had been known from the start might have warranted another solution.

Anyway, here's an example of what I was trying to explain earlier:

Pattern VAR BYTE
StepInPattern VAR BYTE

Main:
Select Case Pattern
Case 0 : Goto Pattern1
Case 1 : Goto Pattern2
Goto Main

' ------ Subroutine for pattern 1 ------
Pattern1:
Select Case StepInPattern
Case 0
High LED1
StepInPattern = 1 'Next time, next step
Case 1
LOW LED2
StepInPattern = 2

Case 2
HIGH LED2
StepInPattern = 3

Case 3
LOW LED 2
StepInPattern = 0 'Next time, start over
END SELECT
Pause 100
Goto Main

' ------ Subroutine for pattern 2 ------
Pattern2:
Select Case StepInPattern
Case 0
HIGH LED1
LOW LED2
StepInPattern = 1 'Next time, next step

Case 1
LOW LED1
HIGH LED2
StepInPattern = 0 'Next time, start over
END SELECT
Pause 50
Goto Main

Basically it's two statemachines "driven" by the Pattern and StepInPattern variables. In your interrupt routine you can simply reset the StepInPattern variable and set Pattern to what ever pattern you want and it will start that pattern from the beginning. There will be a delay up to the longest Pause time that you've got in there but it won't have to complete the full cycle.

This is not very code effective but it illustrates what I meant.

On a project of mine I have single LED indicating various states of the device. I have a set of patterns defined something like this:

Status_LED_Pattern_OK CON %0000000011111111
Status_LED_Pattern_Fault CON %1010101010101010
Status_LED_Pattern_Wait CON %1111000011110000
I can then select which pattern I want by doing:

Pattern = Status_LED_Pattern_OK

And in my main interrupt I have counter (PatternPointer) incrementing every 1/10th of a second which "points into" the pattern variable and sets the output driving the LED to the state of that bit, like:

PortB.0 = Pattern.0[PatternPointer]

Obviosuly the interrupt code needs to make sure that "pointer" only counts from 0-15 and then starts over or strange things WILL happen.

/Henrik.

Disclaimer: The above is from the top of my head to illustrate the tecnique, it may or may not work exactly as written.

HankMcSpank
- 29th April 2011, 16:41
Hank, I think the hangup for you is how you generate the pattern.

If you use some counters to "decide" when and what led to light up, instead of hanging your program for .4 seconds, you will see the flag check much faster.

Maybe have your interrupt be a time base, like .1mS or something. Then while in the ISR, check for button press and inc your "led counters"

I know this is vague, but all I have time for right now.

Hi Bert,

You're bang on ...using pauses is not optimum, as obviously while it's erhm pausing it ain't doing anything else.

the problem here is I'm already using two timers in my same testbed program here (one for a special event trigger & another to control HPWM) ...ok, so I've 3 timers left (16f1824 has 5 timers) , but I need a whole heap of different pauses (& some quite longish...I've always had an epic fail when I've tried to use timers for long pauses!) ....now I realise I could just reset the timer preload value accordingly each time I need a different pause, but ....aaargh ....brainache. I guess that's what it comes down to... a solution that avoids brainache ...it's a fair cop, I'll get me coat!


Henrik,

Many thanks for the comprehensive examples...I shall study them later tonight.

Cheers,
Hank

cncmachineguy
- 29th April 2011, 17:11
Henrik, I LOVE THAT!!!!!

Hank, I think I like Henrik's idea better, but may or may not work for your app. Are any of the current 2 timers you have ISR's or are they just being used? Cuz if either of them have ISR, you can use that for the timebase. Increment counters there and check for button.

HenrikOlsson
- 29th April 2011, 17:37
If you use TMR2 as the PWM timebase you can use its postscaler as "divider" between the PWM frequency and interrupt frequency. If you have, say, 16kHz PWM frequency and set the postscaler to 1:16 you'll get an interrupt rate of 1kHz which you can use as a timebase if nothing else you already have running is suitable for that.

Again, there are many ways.....

/Henrik.

rmteo
- 29th April 2011, 18:02
...You're bang on ...using pauses is not optimum, as obviously while it's erhm pausing it ain't doing anything else.

If you want to use a PIC (or any MCU for that matter) efficiently, one of the most important things to learn to do - after you have accomplished your first blinky program - is to wean yourself off using pause (blocking) type commands completely.

rmteo
- 4th May 2011, 01:05
....but I need a whole heap of different pauses (& some quite longish...I've always had an epic fail when I've tried to use timers for long pauses!) ....now I realise I could just reset the timer preload value accordingly each time I need a different pause, but ....aaargh ....brainache. I guess that's what it comes down to... a solution that avoids brainache ...

Here is pseudocode that blinks 2 LEDs at different speeds (1Hz and 2Hz) and does not use any pause (blocking delay) type commands. You can easily expand it to any number of LEDs (or tasks) with no performance degradation. It uses a single hardware timer to provide a system tick (also known as a heartbeat). It can be used as a template to create simple multitasking applications.

sub timer1 interrupt
inc(cyctick1)
inc(cyctick2)

if cyctick1 > 500 ' 500mS delay, 1st. counter
cyctick1 = 0 ' reset counter
cycflag1 = 1 ' set flag
endif
if cyctick2 > 250 ' 250mS delay, 2nd. counter
cyctick2 = 0 ' reset counter
cycflag2 = 1 ' set flag
endif
end sub

sub usrfunction1
toggle LED0
end sub

sub usrfunction2
toggle LED1
end sub

main:
cyctick1=0:cyctick2=0 ' Initialize variables
ccyflag1=0:cycflag2=0 ' "

timer1 = 1000 ' set up timer1 to interrupt every 1mS

while true
if cycflag1=1 then ' check if 500mS has expired
cycflag1 = 0 ' reset flag 1
do userfunction1
endif
if cycflag2=1 then ' check if 250mS has expired
cycflag2 = 0 ' reset flag 2
do userfunction2
endif
wend