|
|
View previous topic :: View next topic |
Author |
Message |
Blob
Joined: 02 Jan 2006 Posts: 75 Location: Neeroeteren, Limburg, Belgium
|
is this a way around my calculation delay? |
Posted: Tue May 27, 2008 4:18 am |
|
|
Hello,
moving on into my project...
I have a wheel with 18 teeth on it.
with Timer_DIV_BY_2 i will seek the deviating thooth (position 0) an with interrupt B0 i then increase position to 18 and then back to 0
I use Timer_1_DIV_BY_8 to define the rpm of the rotating disc.
each time position == 0
the value of get_timer1() (=1 rotation)
will be used in a calculation to get the correct rpm to 50 rpm accurate.
I have to take some actions at certain points on the disc at certain rpm.
f.e. at 5000 rpm I have to make the input high at 15 degrees after position 0
@position0:
Code: |
tNow = get_timer1();
set_timer1(0);
setup_timer_1(T1_INTERNAL | T1_DIV_BY_8);
tNow_copy = tNow;
tNow = tNow / 16;
adr = (int16)(46875 / tNow) - 11; | then i will read the value on adr: Code: | wait = read_program_eeprom(adr); |
after this, i will activate timer_3_DIV_BY_4 which will make an output high at interrupt.
Code: | wait = 65535 - wait;
setup_timer_3(T3_DISABLED);
set_timer3(wait);
setup_timer_3(T3_INTERNAL | T3_DIV_BY_4); |
I precalculated all values that i should put in my eeprom.
for example i make my output high at 10° after position 0 from 500 RPM up to 16000 RPM
this works very well till about 11000 rpm
but then I get a deviation up to 4° at 160000 rpm.
I assume this is caused by the time the processor needs to calculate the adr variable (lots of derivations)
of course i can take that into account when i calculate my values to put in eeprom.
but i prefer to do it some other way
i was thinking of starting my timer_3 before i calculate the rpm
after the rpm calculation i do a get_timer3 and subtract it from my wait variable
it would be something like this:
@position0:
Code: |
tNow = get_timer1();
set_timer1(0);
setup_timer_1(T1_INTERNAL | T1_DIV_BY_8);
setup_timer_3(T3_DISABLED);
set_timer3(0);
setup_timer_3(T3_INTERNAL | T3_DIV_BY_4);
tNow_copy = tNow;
tNow = tNow / 16;
adr = (int16)(46875 / tNow) - 11;
wait = read_program_eeprom(adr);
wait = 65535 - wait
wait = wait - get_timer3();
setup_timer_3(T3_DISABLED);
set_timer3(wait);
setup_timer_3(T3_INTERNAL | T3_DIV_BY_4);
|
does this make sence to do?
or is the time needed to set and get the timer even larger or comparable to the division
I ask this because testing it takes lots of time. i have to create a test environment an run lots of test to have som reliable results.
Godspeed,
Blob |
|
|
Ttelmah Guest
|
|
Posted: Tue May 27, 2008 10:34 am |
|
|
The first thing you can do, is get rid of all the timer setups...
Set the timer division ratios etc., just _once_ at the start of your main code. You should never change the division ratio, _after_ reading a timer value, or setting a timer to a value (it could have been running with the wrong divisor for several machine cycles if you do this), but in your case, the value used will be the last setting, so why set it again....
Similarly, why disable the timer? leave it running, and just set it to your required value. Every 'extra' thing you do like this, costs time.
Why 'fiddle' with read_program_eeprom?. Though it'll make no difference to the speed, why not just use a constant array?.
What is 'Tnow_copy' used for. As shown, it just wastes time transferring data into this. If you need a copy, then do the arithmetic into this, rather than having an extra transfer, so:
temp = tNow / 16;
adr = (int16)(46875 / temp) - 1
You don't actually need the cast here. Since the arithmetic value, is >255, int16 arithmetic will already be being used. The compiler is smart enough to deal with this though.
What is the range of expected values for Tnow?. Will depend on your actual clock rate etc..
If you are doing just this one job, why interrupt?. It is _much_ faster, to simply sit in a tight loop, and wait for PIN_B0 to drop. So:
Code: |
while (input(PIN_B0)==0) ; //wait for pin to go high
while (input(PIN_B0)==1) ; //Now advance, when the pin drops
tNow=get_timer1();
|
You will typically get the timer value better than 25 instructions _sooner_, than by using an interrupt...
Best Wishes |
|
|
ckielstra
Joined: 18 Mar 2004 Posts: 3680 Location: The Netherlands
|
|
Posted: Tue May 27, 2008 7:08 pm |
|
|
Quote: | but then I get a deviation up to 4° at 160000 rpm. | I guess you mean 16k rpm?
A 4° error is about 1%.
1 rotation at 16k rpm takes 3.75ms
1% of 3.75ms = 37.5us
Your processor runs still at 20MHz?
Than 37.5us == 20MHz / 4 * 37.5 = 150 instruction times.
150 instructions is not a lot. Any improvements you can make to the calculations will increase timing accuracy.
Besides Ttelmah's suggestions another improvement is to start the strobe light using the CCP module in compare mode. The CCP module can be configured to automatically set the strobe output high or low on timer3 reaching a certain value. This has the advantage that the strobe light is immediately activated, eliminating the constant interrupt overhead of 25+ instruction. You still use an interrupt to switch off the strobe light but this is now the CCP interrupt instead of the timer interrupt.
A required hardware change is moving the DigitalOutput pin for controlling the strobe light to the CCP1 output pin (C2).
In the code below I modified your Timer3 interrupt for using the CCP and also removed some calls for starting and stopping of timer3.
Code: | #INT_CCP1
void ccp1_handler()
{
if (ign)
{
// output_high(DigitalOutput); // Not needed anymore, line is already activated by CCP module.
// Configure CCP module to turn off strobe light.
set_timer3(0);
setup_ccp1(CCP_COMPARE_CLR_ON_MATCH | CCP_USE_TIMER3); // Sets strobe output low on match
CCP_1 = 36; // fire after 36 ticks of Timer3 (28.8 us)
ign = false;
}
else
{
// output_low(DigitalOutput); // Not needed anymore, line is already de-activated by CCP module.
setup_timer_3(T3_DISABLED);
setup_ccp1(CCP_OFF);
}
} |
In your timed_flash function you change: Code: | wait = 65535 - wait;
setup_timer_3(T3_DISABLED);
set_timer3(wait);
setup_timer_3(T3_INTERNAL | T3_DIV_BY_4); | to: Code: | set_timer3(0);
CCP1 = wait;
setup_timer_3(T3_INTERNAL | T3_DIV_BY_4);
setup_ccp1(CCP_COMPARE_SET_ON_MATCH | CCP_USE_TIMER3); // Activate strobe
|
And in the program start change: Code: | enable_interrupts(int_timer3);
| to: Code: | enable_interrupts(INT_CCP1);
|
|
|
|
Blob
Joined: 02 Jan 2006 Posts: 75 Location: Neeroeteren, Limburg, Belgium
|
|
Posted: Wed May 28, 2008 2:42 am |
|
|
Hello Ttelmah,
thanks for your comments!
I will eliminate the timer setups and let the timer running, and use a boolean in the interrupt to "say" if it may go through the loop.
Tnow_copy will be used later on, but in fact i can use temp for that as well.
I used the disable function so i would not get undesired interrupts.
as you say an interrupt will take about 15 cycles to go to and return from the sub routine.
so i used the disable function so that i would not get that loss of stack saving etc.
also it was not really clear what the "disable" does, if I interprete your text correctly, it only stops the timer from running and all settings will remain?
By setting a timer value the timer will run again?
Quote: | If you are doing just this one job, why interrupt?. It is _much_ faster, to simply sit in a tight loop, and wait for PIN_B0 to drop. |
I will try this!
I'll make a while(running) loop
and then polling on the input
I used the interrup thing because I have to do lots of things during 1 rotation.
and i was thinking that during the while i could not doing anything but that is not truth
Code: | i can do this while running:
while (input(PIN_B0)==0) ; //wait for pin to go high
position ++; // determine which tooth i am on
if (position == 1)
{
//read timer and determine rpm
}
if (position == 2)
{
//read analogue 1
{
etc etc
while (input(PIN_B0)==1) ; //Now advance, when the pin drops |
here is a list of what i should do in 1 rotation:
-2 outputs reacting on timer_3
-2 outputs reacting on a timer (i think i will use the ccp as ckielstra mentions)
-reading 2 digital inputs
-reading 7 analogue inputs
-2 digital outputs reacting on analogue readings
-reacting on 2 hardware interrupts
Godspeed
Blob
Last edited by Blob on Wed May 28, 2008 6:17 am; edited 1 time in total |
|
|
Blob
Joined: 02 Jan 2006 Posts: 75 Location: Neeroeteren, Limburg, Belgium
|
|
Posted: Wed May 28, 2008 2:54 am |
|
|
Also many thanks to ckielstra! (bedankt )
giving me the idea of using the CCP.
I never used this before.
I will do some reading about it, to better understand it.
If you don't mind i will come back on this when i don't get it working myself
yes, it is 16K not 160K
Godspeed,
Blob |
|
|
Ttelmah Guest
|
|
Posted: Wed May 28, 2008 7:33 am |
|
|
If you want to stop the interrupts, then stop these, rather than the timer. Just use disable_interrupt, and enable_interrupt calls.
Typically, calling an interrupt, takes about _30+_ instructions (not 15 cycles), and the same again to come back. Reckon on 65 instruction times in total, even without any code in the actual 'handler'. the '25' figure, is the difference between the overhead in calling the interrupt, and the 'worst case' delay in the test and loop, if the edge changes the very instruction after the test. Sometimes the gain will be more like 30 instructions.
You could also, simply test the interrupt flag, and still use the interrupt hardware. Just disable the global interrupt enable, clear the interrupt flag, and loop testing this. When it gets set, the event has happened (avoids missing an edge, if the pulse is narrow). Then, have the CCP, set to clear it's output on it's trigger, put the time left into the CCP register, set the output, and it'll automatically clear the specified time latter.
If you want to post some figures, for the actual clock rate of the processor, and what delays you want, we may be able to advise on a 'better' route to get the numbers.
Best Wishes |
|
|
Blob
Joined: 02 Jan 2006 Posts: 75 Location: Neeroeteren, Limburg, Belgium
|
|
Posted: Wed May 28, 2008 8:51 am |
|
|
Wow,
i feel realy excited, so little known, so much to learn!!!!
these injections of wisdom realy ingnites my interests and respect for the PICs !!
And in fact all is logical... (and even highlighted in the datasheet)
reading the logic diagram in the datasheet cleares a lot too.
so i just have to disable int0,
i clear INT0IF with:
and start polling on INT0F, each time it comes up i clear it again.
INT0IF will only be set at the edge i define in my main.
(also datasheet)
The datasheets appears to be a very important document....
Thanks for clearing some things.
Godspeed,
Blob |
|
|
Blob
Joined: 02 Jan 2006 Posts: 75 Location: Neeroeteren, Limburg, Belgium
|
|
Posted: Tue Jun 03, 2008 6:52 am |
|
|
Hello all,
here is a schematic about my project
Thanks to your comments so far, i think it is best for me to use these functions:
-use INT0 hardware and loop on the INT0IF flag => this works fine
-use timer_3 interrupt for the flashing light (it may only flash for about 20us) in the shaded area => this works
-use CCP_1 in compare modus to activate the output during the programmed value (dotted area) starting at a fix point => I'm working on this
the output device is active when it gets a mass signal, so output high -> inactive, output low -> active
-in the future (i hope the near future) I will add the analogue inputs.
This is now my Position loop as Ttelmah suggested:
(without the startup)
Code: |
void PositionLoop()
{
while(true)
{
// else //normal run, after startup
// in startup i define my position at that moment as 3
{
while(!INT0IF);
INT0IF = 0;
pos++;
if (pos == 1)
{
//read analogs in future
}
else if (pos == 2)
{
CCP_1 = 1875 + get_timer1();
setup_ccp1(CCP_COMPARE_SET_ON_MATCH);
//i compare with Timer1 so i dont have to or anything
//timer1 is set as div by 8 in the start loop (not displayed)
//I now use a fixed delay of 3 ms to test
//my output must become high 3ms after pos == 2
//so i add 1875 to my get_timer1()
}
if (pos == 18)
{
set_timer1(0);
if(!digi)
flash();
pos = 0;
}
else if (pos == 15)
{
timed_flash(); // i use timer3 here (see other posts)
}
}
}
} |
I did set_tris_c(129); so C2 is an output.
also i use this in my main (as suggested in the example file)
Code: |
setup_ccp1(CCP_COMPARE_SET_ON_MATCH); // Configure CCP1 to set
CCP_1=0; // C2 high now
set_timer1(0); |
and i don't use output_high/low
what is see on my scope is the following:
each time at position 2 my output drops because i setup_ccp1(CCP_COMPARE_SET_ON_MATCH); => C2 is low by default
this is ok
The thing that is not working like i want it to work is the time after when C2 will be high again (off)
it does not match the 1875 (3ms) I put in. it is about 35 ms instead.
I think i am missing something in the ccp setup...
Godspeed,
Blob |
|
|
Ttelmah Guest
|
|
Posted: Tue Jun 03, 2008 10:01 am |
|
|
As a comment, you seem to think that using 'CCP_COMPARE_SET_ON_MATCH', will clear the CCP pin. It won't. _You_ have to clear the pin yourself, then the CCP, will set it.
Have a look at tip10, in the MicroChip 'tips&tricks' application note, for the CCP. They generate a 16bit PWM pulse from the CCP, using the timer1 interrupt, to set the pin, then the CCP in compare and clear mode, to clear the pin. This shows exactly what you will need to do (except that you would clear the pin, and use the CCP to set it).
Best Wishes |
|
|
Blob
Joined: 02 Jan 2006 Posts: 75 Location: Neeroeteren, Limburg, Belgium
|
|
Posted: Tue Jun 24, 2008 6:58 am |
|
|
Hello again,
my project is running well at the moment, thanks for the help so far!
I have a question regarding timer 3 interrupts.
If i disable the interrupt and after some time reanable it,
is it correct that i first have to clear the TMR3IF bit?
for example my interrupt is enabled and will perform a subroutine.
after this i disable the interrupt and go on with other code.
the timer keeps counting, so when it has an overflow it wil set TMR3IF high.
if i reanable the interrupt, it sees that the TMR3IF is set, and so it will immediately perform the subroutine...
Is my way in thinking here correct?
Thanks in advance,
Godspeed,
Blob |
|
|
Ttelmah Guest
|
|
Posted: Tue Jun 24, 2008 8:25 am |
|
|
Think of it as:
Events - what will set the flag. In some cases can be changed.
Flags - set when the event occurs.
Masks - first the one for the interrupt itself.
Then the one for all the interrupts.
The 'handler' is called, when the flag, and the masks are all set.
The flag is set whatever is done to the mask, when the hardware 'event' occurs.
So, you don't 'have' to clear the interrupt flag, if it is set, but if you don't, and the flag is set, the handler will immediately be called.
This is vital in many cases to the handling of interrupts. Remember that the hardware automatically disables the global interrupt flag, as soon as the global handler is called. This prevents other interrupts from interrupting the existing handler, but their flags will still become set if their event occurs, and they will be dealt with as soon as you return from the first event. Similarly, it is common in software to temporarily disable interrupts, so (for instance), if reading a int32 number, in the 'main' code, from a buffer updated in an interrupt, you should disable the interrupt during the read, and immediately enable it afterwards. This avoids the awkward situation of the value changing half way through the read, but nothing is missed, provided the read only takes a few machine cycles, since the hardware flag still becomes set.
Best Wishes |
|
|
Blob
Joined: 02 Jan 2006 Posts: 75 Location: Neeroeteren, Limburg, Belgium
|
Nex Xtal needed |
Posted: Mon Oct 13, 2008 6:37 am |
|
|
Hello Folks,
Thanks for your tips so far,
My rotating disc project is close to be finished.
But now I want to do the same for higher rpm.
At the moment my code is able to run up to 12000 rpm and then the system begins to miss some inputs.
I can see this because the output jumps over one tooth now and then.
so I decided to clock it up to the maximum clock rate.
I use a PIC 18f2520
first i used a 20MHz Xtal
now I use a 10 MHz Xtal in H4 mode.
all functions work well.
Only I have a problem with my Timer_1
originally I used T1_INTERNAL | T1_DIV_BY_2 in the start loop
with my new Xtal this became T1_INTERNAL | T1_DIV_BY_4
For my normal running I used T1_INTERNAL | T1_DIV_BY_8
so I should use T1_INTERNAL | T1_DIV_BY_16 but this does not exist on the 2520
T1_Internal = (40 MHz/4) = 10 MHz and this is the same as T1_external. So it is not possible to use Timer 1 because it can not be divided by 16.
Or is there a way to work around this?
Godspeed,
Blob |
|
|
Ttelmah Guest
|
|
Posted: Mon Oct 13, 2008 7:15 am |
|
|
Clock the timer directly off the system source clock.
Use an external clock module, rather a 'crystal'. This is then able to drive multiple inputs. Feed the main system clock off this, and select H4. The CPU will then be running at 40MHz. Connect the Timer1 input too it as well, and setup timer1 to use the external clock, and divide by 8. It'll then be running off the 10Mhz without the PLL.
Best Wishes |
|
|
Blob
Joined: 02 Jan 2006 Posts: 75 Location: Neeroeteren, Limburg, Belgium
|
|
Posted: Mon Oct 13, 2008 7:27 am |
|
|
hello Ttelmah,
as I understand it correctly:
by using a xtal of 10 MHz and setting H4 mode,
when I choose T1_EXTERNAL, the source of timer 1 will be 40 MHz instead of 10?
Thanks,
Blob |
|
|
Ttelmah Guest
|
|
Posted: Mon Oct 13, 2008 12:50 pm |
|
|
No, if your external clock is 10MHz, the timer1, will be running off 10MHz, which then allows you to use your 10MHz divisors, while still having the CPU running at 40MHz.
Best Wishes |
|
|
|
|
You cannot post new topics in this forum You cannot reply to topics in this forum You cannot edit your posts in this forum You cannot delete your posts in this forum You cannot vote in polls in this forum
|
Powered by phpBB © 2001, 2005 phpBB Group
|