Multitasking on Picaxe by using the time interrupt

womai

Senior Member
This is a modified copy of my posting regarding the slow-speed PWM:

The routine below calls an interrupt at regular time intervals. The period can be modified by changing the EVENTS_PER_SEC constant.

Overall, no need to use an external chip (555 or the like), this solution has the Picaxe do everything. At the same time it is applicable to any case where you want to execute a piece of code in the background at regular intervals - as close to multitasking as the Picaxe can get. Just replace the toggle command with anything you like. If you make it a selection based on a counter (you can even use timer modulo some the number of tasks) you can implement (non-preemptive) multitasking in a round-robbin manner, i.e. first interrupt calls subroutine 0, second calls subroutine 1, and so on, then start again with subroutine 0. The only requirement is that each subroutine MUST be well-behaved, i.e. finish and return before the next interrupt comes in.

Wolfgang


Code:
' uses internal timer to do slow-speed PWM on pin 0 (needs 28X1)

symbol EVENTS_PER_SEC = 240 ' set to twice the required PWM frequency (actually, slightly higher because of interrupt overhead)

symbol DUMMY_VAL = 65536 - t1s_16
symbol TIMER_PRELOAD_VAL_DIFF = DUMMY_VAL / EVENTS_PER_SEC
symbol TIMER_PRELOAD_VAL = 65536 - TIMER_PRELOAD_VAL_DIFF


setfreq em16

settimer TIMER_PRELOAD_VAL ' set time preload value
gosub timer_setup

eternal_loop:
    pause 4000 ' sleep for 1 sec, or do something else
goto eternal_loop

interrupt:
    toggle 0 ' do whatever is needed, in this case toggle pin to produce square wave output
    gosub timer_setup
return

timer_setup:
    timer = 0xffff ' generate interrupt at next overflow
    toflag = 0 ' clear timer overflow flag
    setintflags %10000000,%10000000 ' interrupt on timer overflow
return
 

rbwilliams

New Member
Wolfgang,
This looks like it could be something I could really use. Although, I really don't understand whats going on.

Can you explain the timing lines more? Especially the symbols. That would be a great place to start.

I really appreciate you posting this code!

-Roger
 

womai

Senior Member
First recommendation, have a thorough reading of the sections in the Picaxe Basic manual explaing the counter as well as the counter interrupts.

Basically the counter gets incremented at regular intervals, and once it reaches its maximum value (0xffff hex = 65535 dec) and wraps back to zero, the program is set up to generate an interrupt (i.e. the interrupt subroutine is called). So to get e.g. 100 interrupts per second, you need to figure out what you have to set the counter to at the beginning so it will already wrap around after 10 milliseconds. The Programming Editor has a few suitable constants alrwady defined (e.g. preset for 1 sec time between wraparound), which I use in my calculations.

As to the term "well-behaved" I used: I would also assume that the interrupt routine cannot get called before it returns (although I did not explicitly try this). But what I meant was that your results (repetition rate) will not be what you want if the routine takes too long to complete.

Wolfgang
 

rbwilliams

New Member
Wolfgang

I reread the SETINTFLAGS, SETTIMER commands. These are still very confusing to me. But I get the gist of preloading a value into the timer. I also get the calling of sub "interrupt:". I didn't understand before that SETINT automatically looks for "interrupt:".

I still wonder what symbol DUMMY_VAL is doing.

Roger
 
Last edited:

Jeremy Leach

Senior Member
I think Dummy Val is used only because of a limitation in picaxe symbol definitions : you can only have one arithmetic operator per definition.
 

womai

Senior Member
Actually the main problem is that the Programming Editor does not know precedence and does not allow parentheses in the symbol statements. So I have to split the calculation into several steps where none of them need those features:

symbol DUMMY_VAL = 65536 - t1s_16
symbol TIMER_PRELOAD_VAL_DIFF = DUMMY_VAL / EVENTS_PER_SEC
symbol TIMER_PRELOAD_VAL = 65536 - TIMER_PRELOAD_VAL_DIFF

instead of

symbol TIMER_PRELOAD_VAL_DIFF = (65536 - t1s_16) / EVENTS_PER_SEC
symbol TIMER_PRELOAD_VAL = 65536 - TIMER_PRELOAD_VAL_DIFF

or even shorter

symbol TIMER_PRELOAD_VAL = 65536 - ((65536 - t1s_16) / EVENTS_PER_SEC)

BTW, my reference to the Picaxe manual was in no way meant to be dismissive - it's just that it contains a lot of information that would be pointless to repeat here. Hope you took it in that vein.

Wolfgang
 

rbwilliams

New Member
is this what you are saying:

symbol EVENTS_PER_SEC = 240
symbol DUMMY_VAL = 62500 (because t1s_16 = 3036)
symbol TIMER_PRELOAD_VAL_DIFF = 260.41666666666666666666666666667
symbol TIMER_PRELOAD_VAL = 65275.583333333333333333333333333

Does the timer increments every 261 pulses?
How does PICAXE do decimals?
 

womai

Senior Member
Yes, you got the idea. Basically, the higher the preload value, the shorter the timespan to the next interrupt. Then, inside the interrupt routine the program needs to set the counter back to the desired preload value (otherwise it would start out at zero instead). There is some small overhead because of that in the interrupt routine, so the true repetition rate is somewhat lower than the intended one; for utmost accuracy, you'd need to time your repetition rate (look at the toggle frequency of the output pin) and correct EVENTS_PER_SEC accordingly.

The Picaxe does no decimals, only integer. So. e.g. 12 divided by 5 yields 2, not 2.4. Just think of it as the Picaxe "forgetting" the decimals at every step:

symbol EVENTS_PER_SEC = 240
symbol DUMMY_VAL = 62500 (because t1s_16 = 3036)
symbol TIMER_PRELOAD_VAL_DIFF = 260
symbol TIMER_PRELOAD_VAL = 65276

Because you can use only integer preloads, in many cases - whenever the division has a remainder - the repetition rate is only an approximation to EVENTS_PER_SEC. The higher the repetition rate, the larger the relative mismatch can become.

One more note, the code above assumes you are running the Picaxe at 16 MHz. If you use a different frequency, you will need to change the constant (e.g. t1s_4 if you are running at 4 MHz).

Wolfgang
 

rbwilliams

New Member
Wolfgang,

I am getting it now. Thanks for all the replies!
If I wanted a more accurate count/sec. input, would I need to use an external timer chip that would do decimal conversion?
As in accurately counting a 50% duty cycle frequency of 0.83Hz?
I am interested in seeing if the PICAXE can count this relatively accurately (+/- 5%) and seeing the result in the toggled LED.
-Roger
 

womai

Senior Member
For input frequencies of around 1 Hz as you state, a half-period is 0.5 sec. To measure that to +/-2.5% means a necessary resolution 0.025 * 0.5 = 0.0125 sec, or a sample rate of 80 Hz. (you need 2.5% because you have error in both measuring the high half-period and the low half-period, which worst case will add up). This is easily achievable with the interrupt method, especially when running the Picaxe on 16 or 20 MHz and once you calibrated out the interrupt overhead. I don't think you'll need any further hardware. To be on the safe side, I'd maybe go for a sample rate of 200 interrupts per second.

Wolfgang
 

rbwilliams

New Member
Wolfgang,
I am very pleased to know that you feel that your interrupt concept could be implemented in my idea.
Could I bother you to go one step further, and possibly show me a pseudo-code example of what the necessary values need to be in order to achieve the results? Also, what do you mean by "interrupt overhead"?

Thanks again!

Roger
 

womai

Senior Member
"Interrupt overhead" - ideally, the interrupt routine would get called and instantly re-initialize counter with the preload value. In reality, the re-initialization will take some finite amount of time, which is not taken into account when calculating the necessary preload value. As a result, the interrupt will get called at slightly larger intervals than you intended. Of course you can correct that by measuring the true repetition rate and then increasing the preload value by a specific amount.

As for measuring the duty cycle:

Reset two counter variables (e.g. w0 and w1) to zero.
Determine (and remember) the state of your input pin.
Set a variable holding the number of input pin state changes to zero.

In the interrupt routine, whenever it gets called, check if the state has changed (i.e. rememberd state <> current state). If yes, increase the state change variable by 1.

Remember current state (overwrite previously remembered state).

If the state change variable is between 1 and 3 and current state is low, increase w0 by 1.

Else if the state change variable is between 1 and 3 and current state is high, increase w1 by 1.

else if state change variable = 3 (meaning you went through one full cycle, once high and once low), calculate the positive duty cycle as duty_cycle_pos = w1 / (w0 + W1), and reset w0, w1, and the state change counter back to 0.

Re-load timer with preload value. Set up interrupt.
 

rbwilliams

New Member
Hello again Wolfgang,

I tried your code with EVENTS_PER_SEC = 240, and it works great.
I then tried EVENTS_PER_SEC = 15 and it works.
But, I get a Stack error at EVENTS_PER_SEC = 20, and Error 6 at EVENTS_PER_SEC = 100.
What does this mean?


Thanks,
Roger
 

womai

Senior Member
Strange, I can't reproduce those errors. I went down to 4 events and up to 200, looks fine.

Couldn't hold myself and coded up the duty cycle measurement. The code is below. It takes the input signal at input pin 0, and gives visual feedback (copy of the input signal) at output pin 0 (those two pins are physically different) so you can see when the Picaxe "sees" the signal.

The duty cycle (in percent) is sent to the PC with sertxd, so you can use the serial terminal in the Programming Editor to display it (set speed to 19200 baud).

I tested it with a 1 Hz input signal (from an arbitrary waveform generator), varying the duty cycle from 5% to 95% and the measured value was within +/-1% of the true value. The routine will run into trouble (overflow) if the signal period is too slow; generally, you should set EVENTS_PER_SECOND to somewhere around 100 to 200 times your signal frequency for this level of accuracy.

The nice thing is that the measurement runs fully in the background, so you can do something else in the main program. The duty cycle is accessible from there as well. You may want to implement some handshaking (setting/resetting a variable) between the interrupt routine and the main program to tell the main program when the duty cycle value has been updated.

Wolfgang

Code:
symbol EVENTS_PER_SEC = 100 ' set to approx. 100 to 200 times the expected signal frequency

symbol DUMMY_VAL = 65536 - t1s_16
symbol TIMER_PRELOAD_VAL_DIFF = DUMMY_VAL / EVENTS_PER_SEC
symbol TIMER_PRELOAD_VAL = 65536 - TIMER_PRELOAD_VAL_DIFF

symbol low_cnt = w0
symbol high_cnt = w1
symbol state_changes = b4
symbol previous_state = b5
symbol current_state = b6
symbol duty_cycle = w8

symbol input_pin = pin0
symbol output_pin = 0

setfreq em16 ' assumes 16 MHz external resonator

gosub counter_setup

settimer TIMER_PRELOAD_VAL ' set time preload value
gosub timer_setup

eternal_loop:
    pause 4000 ' sleep for 1 sec, or do something else
goto eternal_loop


interrupt:

    previous_state = current_state
    current_state = input_pin
    
    if current_state <> previous_state then ' see if input signal state has changed
        inc state_changes
    endif
    
    ' count duration of one high half period and one low half period
    ' we don't know which comes first (high-low or low-high)
    if state_changes > 0 and state_changes < 3 then 
        if current_state = 0 then
            inc low_cnt
            low output_pin ' visual feedback (can be omitted)
        else
            inc high_cnt
            high output_pin ' visual feedback (can be omitted)
        endif
    endif
    
    ' after a full period, calculate and transmit duty cycle
    if state_changes = 3 then
        low_cnt = low_cnt + high_cnt
        duty_cycle = high_cnt * 100 / low_cnt ' calculate in %
        sertxd (#duty_cycle, "%", cr, lf)        
        gosub counter_setup
    endif

    gosub timer_setup
    
return

counter_setup:
    low_cnt = 0
    high_cnt = 0
    state_changes = 0
    current_state = pin0
return

timer_setup:
    timer = 0xffff ' generate interrupt at next overflow
    toflag = 0 ' clear timer overflow flag
    setintflags %10000000,%10000000 ' interrupt on timer overflow
return
 

rbwilliams

New Member
Actually, I think I know what I need - but I am still not sure how to get there.

What I would to do with your code is change EVENTS_PER_SEC into a variable. So I would create some other code to update the value of this variable, then run the timer code using the updated value.

I tried this with variables b1 and w1, but the Editor won't let me. It says 'incorrect symbol - "variable" '
What gives?
 

womai

Senior Member
It would make troubleshooting much easier if you would post the code - it's hard (as in "impossible") to just guess what it looks like.
 

rbwilliams

New Member
Ok. Here is my version of the interrupt code:

Code:
let w1 = 50
symbol TIMER_PRELOAD_VAL = w5

let w2= 65535 - t1s_16
let w3 = w2/w1
let w5 = 65535 - w3
setfreq em16

settimer TIMER_PRELOAD_VAL ' set time preload value
gosub timer_setup

eternal_loop:
    pause 4000 ' sleep for 1 sec, or do something else
goto eternal_loop

interrupt:
    toggle 0 ' do whatever is needed, in this case toggle pin to produce square wave output
    gosub timer_setup
return

timer_setup:
    timer = 0xffff ' generate interrupt at next overflow
    toflag = 0 ' clear timer overflow flag
    setintflags %10000000,%10000000 ' interrupt on timer overflow
return
I got the variable maths working. But now I get a stack error again.

The idea here is that some other code(to be worked out later) will update the value of
w1, and then reset the timer preload value accordingly.
 

rbwilliams

New Member
Has anyone gotten Wolfgang's code to work in Editor version 5.2.0?

It doesn't work for me. I FINALLY understand the code, so now I know what to expect - the output pin0 on a 28X1 in the Editor should toggle at a visible rate (not accurately compared to the desired PWM speed).

It doesn't in my Editor.

Also, If I decrease the speed, I get an error6 overflow message and the Editor closes on me every time.

Very frustrating.

-Roger
 

womai

Senior Member
Strange. I just verified, my Programming Editor version is 5.2.0, and I very definitely downloaded and ran the code I posted...
 

rbwilliams

New Member
I am running Windows XP. I have just installed a fresh copy of 5.2.0 on a different machine and it still doesn't work.
With an EVENTS_PER_SEC value of 10, the program freezes up.

I give up.
 

eclectic

Moderator
rbw.
Don't give up yet!
I know nothing about the program, but I've just tried the following:

PE 5.20, 28X1 (ver A2), 16MHz resonator.

All input pins grounded with individual 10k.
Input 0 connected via switch to V+
LED/res. on output 0

Appears to work fine. LED flashes in response to input.

e
 

Attachments

Last edited:

BCJKiwi

Senior Member
I've been following this thread also and decided to test it.
Used a 28X1 Rev A.1 without external Resonator (em16 line remmed out).

The original code continually flashes a LED on OUT0 regardless of the state of IN0 (no pulldown, 10k pulldown, switched to V+, switch open - all make no difference).

However the code from Post #14 works as described by eclectic in post #22 provided there is a 10k pulldown on IN0.
 
Last edited:

rbwilliams

New Member
Eclectic, BCJK,

Thanks for the update.

I have not tried the program in-circuit, only in the Editor simulator. Perhaps that is the problem. I only have an XXM protoboard, but I should invest in a 28X1 board as well.

BTW, I am developing a new code based on Wolfgang's idea, but using a bunch of words instead of settimer for use in an 08M.
 

womai

Senior Member
Interesting bit of information:

Just changed thew software of my Picaxe based scope to use the timer interrupt to generate the sample clock for slow sample rates (because PWM has a lower frequency limit, previously I had to play tricks - lowering the internal oscillator frequency, which is a pain because that changes the serial data rate as well).

On a 28X1 running at 20 MHz I was able to achieve up to 1200 Hz event rate (the interrupt routine only executes a short pulsout). That's just enough for what I needed (1000 Hz and slower), so I'm pretty happy.

I also measured the overhead of the interrupt routine, worked out to 90 microseconds, virtually independent of the number of event per second. So the time preload value has to be set to:

preload = 65536 - 78125 * (1/f - 90 us)

The 78125 is the 1 second count rate based on 20 MHz clock frequency. I measured a few actual repetition rates and they were almost exactly where they should be (less than 0.1% frequency error).

Wolfgang
 

BCJKiwi

Senior Member
Presume the code is doing some smarter math than 65535 - 78125 (which would produce an illegal -ve value) to achieve the desired result?
 

womai

Senior Member
Actually the Picaxe does not perform the calculation (which indeed would exceed the possible range of a 16-bit integer). I did it in Excel and then simply hardcoded the results in the Picaxe program.
 

John West

Senior Member
Actually the Picaxe does not perform the calculation (which indeed would exceed the possible range of a 16-bit integer). I did it in Excel and then simply hardcoded the results in the Picaxe program.
That's cheating!

I'll remember that trick. Thanks.
 

ACN

New Member
Problems found when timing and using sertxd

Hi Womai, I've tested your routine in a 28X1@4MHz and it worked fine until I tried to insert the sertxd command.

I suppose sertxd freezes the timer for a while and the longer timing arrive.

Here are attached my two osciloscope screens (one without sertxd and the other with).

I also attach the program I tested.

I'm using the picaxe for my degree project and any help would be appreciated.

Thanks in advance.
 

Attachments

hippy

Technical Support
Staff member
What specific help are you looking for ACN ?

Adding SERTXD or SEROUT into a time critical routine will cause problems as what it sends takes time to send. You send up to 10 characters which is up to a total time of at least 20ms at 4800 baud at 4MHz.
 

ACN

New Member
Hi Hippy,

What I try to do is to measure an ADC input several times (10 times is OK), make the average and send the data. It has to be repeated every 100ms and it has to be accurate enough. If the time sertxd spend sending is not evaluable (or not fixed) is interesting for me to know for using another way.

I use this for a rpm measure in a small engine that accelerates an inertia disk.
The data is captured in an excel spreadsheet and the slope of the curve determine the acceleration and considering the inertia is obtained the torque.

That is used as an engineering degree project, and I'm trying to do it as simple (and cheaper) as possible.

If sertxd consumes time (and if it is not evaluable) I can send the data to a memory (via I2c) and rescue it later, or use one PIC to acquire the data and another to send it.


Thanks in advance.

ACN
 

womai

Senior Member
One thing to try would be to use hserout instead of sertxd. Remember to invert the drive data polarity (in the hsersetup command), then you can get away without a MAX232 translator chip. Of course you need to hook up the serial cable to the USART TX pin (port C.6) instead of the download TX pin.

The hardware serial command can send the data in the background (the chip has this implemented in hardware), so chances are the timer does not get disabled during that.

Wolfgang
 
Top