# Better method than polling a timer?

#### spinnaker

Joined Oct 29, 2009
7,835
So this question has to deal with my POV hardrive clock. If a mod wants to merge this thread with the other then it fine but I thought it best to keep them separate since this subject is different enough and hopefully others can learn with it without the confusion of the other thread.

As a refresher I have a rotating disk with numbers, Under the numbers are 8 LED positions. I have a hall effect home sensor that uses the CCP to determine the rotation speed of the disk. The LEDs should fire at the appropriate time to show a POV clock. As the disk moves to each "position" more LEDs will fire at the same time with the possibility of 8 LEDs all firing at the same time.

I have the rotation speed calculation working perfectly, thanks to the help of a number of members of this forum.

Now it comes to lighting the LEDs at the appropriate time. My first thought was to start a timer interrupt each time the CCP interrupt hits. This worked great for a single position and a single digit but I don't think that is going to work with multiple positions.

I then came across the idea of polling a timer in the main loop. But the problem I seem to be running across there is that by the time I poll the timer, it has moved on and never equal to the value I was looking for.

I was thinking I could look for a range of values. Is this an appropriate way to do this? Or is there a better way?

I was thinking that this is really nothing more than a propeller clock project. I do plan to do more research to see how others have don e it but wanted to get some input first. There is a lot of bad code out there. One of which I saw was the delays between positions where hard coded and the is certainly the wrong way to go.

Joined Mar 10, 2018
4,057
What is the time expended, in uS or mS, between positions as the disk rotates ?

Generally speaking interrupts make sense, but only if code within interrupt does not
cause a lot of stack push = latency. Don't call f()'s inside ISR, use pointers to memory
are key. Wherever possible set a flag and exit. Under certain situations inline ASM
could be beneficial.

There is HW approach, use logic fabric inside a PSOC and either schematic capture
of its onboard resources or Verilog to design a more HW approach. Just a thought.

Regards, Dana.

#### spinnaker

Joined Oct 29, 2009
7,835
Micro seconds between positions. The disk rotates > 3000 rpm.

No idea what you re referring to in most of the rest of it. I am already using a pic so not interested in other hardware.

#### BR-549

Joined Sep 22, 2013
4,928
Not sure of what we're talking about, but there would be no need to fire the LED position, on every RPM, right?

#### spinnaker

Joined Oct 29, 2009
7,835
Not sure of what we're talking about, but there would be no need to fire the LED position, on every RPM, right?

Each position (there are 8) may need to be fired every rotation. All positions may need to be fired at the same time.

Disk rotates clockwise.

Here is some simplified pseudo code of how I think it is supposed to work.

Ticks per rotation is measured (this part works perfectly).

Home sensor is tripped.
A delay of some period for the zero character to get over first position.
P.S> the home sensor is on the right side of the disk so this will be the longest delay. I figured more time for setup but I can reverse rotation or add another sensor on the other side of the board.

If appropriate the LED is fired to light zero in the first postilion..
Disk moves on to 2nd position.
Now 1 is over position 1 and zero over position 2
If one is supposed to light then LED 1 is fired.
If zero is supposed to be lite then LED2 is fired.
Disk moves on
Now three is over position 1, one is over position two and zero is over position three.
If numbers are supposed to light in those positions then those LEDs are fired.
Now four is over position one and so on

Hopefully I have described it effectively.

Here is the disk that is mounted on the hard drive spindle. It is not the exact disk as I have made some design changes but it is close to to what I have now. The circled bit is a magnet for the hall sensor. I have moved it inboard and under the zero to hopefully make timing easier. and the numbers are more spread out in the new design.

Not sure if you can make it out but this is the LED board. There are 2 1Watt LEDs at each position for a total of eight positions.

Last edited:

#### nsaspook

Joined Aug 27, 2009
10,678
I was thinking that this is really nothing more than a propeller clock project. I do plan to do more research to see how others have don e it but wanted to get some input first. There is a lot of bad code out there. One of which I saw was the delays between positions where hard coded and the is certainly the wrong way to go.
I don't think it is certainly the wrong way to go by default. There are times when it's the 'smart' solution when there must be extensive dynamic computation within tight timing restraints. In these conditions the sensor data is used to validate the used constraints of hard coded routines and data procedures from tables or simple precalculated data functions.

#### BR-549

Joined Sep 22, 2013
4,928
Ok, thanks for the info. I was thinking something else. Your description was clear.

Sounds like the need of a positional timing buffer.....that could update every 10 RPM or so.

Just guessing....I won't distract you with my ignorance.

#### djsfantasi

Joined Apr 11, 2010
8,655
So, the LED(s) in each position need to fire when the appropriate digit is above it?

What’s controlling it? A μP? You say that polling a timer delays turning on the LED.

Can you pre-calculate a pattern for the next display? This is a clock, so what’s its resolution. If it displays seconds, does your μP have enough power to calculate this pattern between time changes. Even if the finest time displayed is hundredth of a second, that gives you 10mS in which you could calculate the next pattern. Plus, you don’t have to do this in every refresh cycle.

By pre-calculating, you minimize the cost of polling a timer. And if the μP doesn’t have enough power for two tasks, have you considered using two?

#### nsaspook

Joined Aug 27, 2009
10,678
One way to handle this is to use a ISR driven state machine to time LED flashes with disk digit position. This is not a complete clock program, it's just a demo of one possible way to sequence the lights to disk with a single rotation
trigger signal for a hall sensor detecting a metal tab on the edge of the spinning disk plate.

The Timer 1 interrupt sequences the LED/disk slot or hole timing from sequence data in the main program.
C:
void TMR1_DefaultInterruptHandler(void)
{
// or set custom function using TMR1_SetInterruptHandler()
// line RGB pulsing state machine using current lines pointer

switch (V.l_state) {
case ISR_STATE_FLAG:
WRITETIMER1(L_ptr->strobe); // strobe positioning during rotation
T1CONbits.TMR1ON = 1;
G_OUT = 0;
R_OUT = 0;
B_OUT = 0;
A_OUT = 0;
V.l_state = ISR_STATE_LINE; // off time after index to start time
break;
case ISR_STATE_LINE:
WRITETIMER1(V.l_width);
LED3 = false;
if (!L_ptr->sequence.skip) {
if (L_ptr->sequence.R)
R_OUT = 1;
if (L_ptr->sequence.G)
G_OUT = 1;
if (L_ptr->sequence.B)
B_OUT = 1;
if (L_ptr->sequence.A)
A_OUT = 1;
}

V.l_state = ISR_STATE_WAIT; // on start time duration for strobe pulse
break;
case ISR_STATE_WAIT: // waiting for next HALL sensor pulse
case ISR_STATE_DONE:
default:
T1CONbits.TMR1ON = 0; // idle timer
G_OUT = 0; // blank RGB
R_OUT = 0;
B_OUT = 0;
A_OUT = 0;
V.l_state = ISR_STATE_DONE; // on start time duration for strobe pulse
break;
}
}
The hall sensor triggers the external interrupt 0 ISR and CCP4/Timer 5 validates the rotation speed counts.
C:
void INT0_DefaultInterruptHandler(void)
{
LED1 = ~LED1;
LED3 = 1;
LED4 = 1;
if (PIR4bits.CCP4IF) {
LED5 = ~LED5;
PIR4bits.CCP4IF = 0;
}
if (!V.rpm_overflow) {
if (!V.rpm_update) {
V.rpm_counts_prev = V.rpm_counts;
V.rpm_update = true;
}
LED5 = ~LED5;
}
TMR5_WriteTimer(0);

// line rotation sequencer
// Hall effect index signal, start of rotation

if (V.l_state == ISR_STATE_LINE) { // off state too long for full rotation, hall signal while in state
V.l_full += strobe_adjust; // off state lower limit adjustments for smooth strobe rotation
}
V.l_state = ISR_STATE_FLAG; // restart lamp flashing sequence, off time

/* interlock the main program updates and pointer updates at sequence end */
if (V.update_array && V.update_sequence) {
V.update_sequence = false;
V.l_buffer = ~V.l_buffer; // switch line buffer selector
}
V.update_array = false;

switch (V.l_buffer) {
case 0:
L_ptr = &L0[V.line_num]; // select line strobes data 0
L_ptr_next = &L1[0];
break;
default:
L_ptr = &L1[V.line_num]; // select line strobes data 1
L_ptr_next = &L0[0];
break;
}
V.rotations++;

/* limit rotational timer values during offsets */
switch (L_ptr->sequence.down) {
case false:
L_ptr->strobe += L_ptr->sequence.offset;
if (L_ptr->strobe < V.l_full)
L_ptr->strobe = V.l_full; // set to sliding lower limit
break;
default:
L_ptr->strobe -= L_ptr->sequence.offset;
if (L_ptr->strobe < V.l_full)
L_ptr->strobe = strobe_limit_h;
break;
}
V.line_num++;
if (L_ptr->sequence.end || (V.line_num >= strobe_max)) { // rollover for sequence patterns
V.line_num = 0;
V.update_array = true;
V.sequences++;
}

// start line RGB pulsing state machine using current lines pointer
WRITETIMER1(L_ptr->strobe); // strobe positioning during rotation
T1CONbits.TMR1ON = 1;
G_OUT = 0;
R_OUT = 0;
B_OUT = 0;
A_OUT = 0;
V.l_state = ISR_STATE_LINE; // off time after index to start time
V.rpm_overflow = false;
}
Main program sequencer with some extra code from the original RGB demo.
https://github.com/nsaspook/25k22_hd_pov/blob/v2.00/hdpov25k22.X/pov_mon.c

https://github.com/nsaspook/25k22_hd_pov

#### Sensacell

Joined Jun 19, 2012
3,096
Assuming you are using the PIC CAPTURE / COMPARE engine...

One idea is to pre-calculate all the timing offsets that are needed in your mainline code, then write the simplest, fastest interrupt routine that does only the essential, time critical tasks.

The key to time-critical stuff is to have the ISR do only the stuff that has to happen exactly at the precise time.
The ISR latency and overhead has to be reduced to the absolute minimum, don't do any logic or calculations in the ISR, just update the essentials and return.

Build a simple table in that contains:
(a) A mask for which LED's turn on or off on the next compare match
(b) A pre-calculated offset that you add to the compare register for the next timing cycle.

Then all the ISR has to do is:

(a) lookup which table entry is next.
(b) output the mask, turning on / off the correct LED(s).
(c) Add the calculated offset for the next interrupt cycle.
(d) Increment the table pointer to the next entry.

#### spinnaker

Joined Oct 29, 2009
7,835
Assuming you are using the PIC CAPTURE / COMPARE engine...

One idea is to pre-calculate all the timing offsets that are needed in your mainline code, then write the simplest, fastest interrupt routine that does only the essential, time critical tasks.
This is a good idea. I am already calculating average RPM using an array of 10 samples and a loop but there is no reason I could not calculate the RPM every tenth interrupt. And at the same time calculate my various delays.

But the issue still stands of how to implement those delays. (Still need to look at @nsaspook 's code). I am thinking of coing back to the timer interrupt idea. Home sensor kicks off the first timer interrupt. That interrupt kicks off the next one, then the next one and so on. Of course I will need to keep track of where I am.

#### nsaspook

Joined Aug 27, 2009
10,678
This is a good idea. I am already calculating average RPM using an array of 10 samples and a loop but there is no reason I could not calculate the RPM every tenth interrupt. And at the same time calculate my various delays.

But the issue still stands of how to implement those delays. (Still need to look at @nsaspook 's code). I am thinking of coing back to the timer interrupt idea. Home sensor kicks off the first timer interrupt. That interrupt kicks off the next one, then the next one and so on. Of course I will need to keep track of where I am.
That code uses XC8 2.xx in C99 mode. XC8 2.05 has optimization level 2 now in 'Free' mode so maybe it's worth the effort to upgrade to the latest versions of mplabx, code configurator and compiler.
Unlicensed (Free) versions of this compiler now allow optimizations up to and including level 2. This will permit a similar, although not identical, output to what was previously possible using a Standard license. Virtually all code generation optimizations are now enabled regardless of the license type,

#### spinnaker

Joined Oct 29, 2009
7,835
My other problem is that I am not seeing the LED if I just turn it off and on each rotation. I guess that either the MCU is too fast or the disk is not rotating fast enough but I can't see the LED. I need at least a 50us delay. Between on and off to see the LED. Don't knopw if this is going to be an issue.

#### BobaMosfet

Joined Jul 1, 2009
2,053
There are only 2 relevant questions in your project at this time. Nothing else is considerable until you answer these 2 questions:

How fast does the disk turn-- exactly. (It's an HDD motor, it's accurate. Is it 7,200rpm, perhaps)?
How fast is the micro you wish to use? (fastest clock you can run it at)

After you answer those questions, the time constraints can be considered. Everything in code is about managing different circles of time in harmony with one another so that everything operates at its own speed, and yet everything operates together.

#### jpanhalt

Joined Jan 18, 2008
11,088
If one goes way back, there are workarounds to capturing a timer's results. Why mess with that? Just use capture and be done with it.

I feel like I am repeating myself from previous threads/posts on this topic. Why prolong the pain?

#### spinnaker

Joined Oct 29, 2009
7,835
Instead of polling the timer, I decided to go with individual timer interrupts and basically chaining them. Below is a simplified test. Waht I am doing hers is calculating the correct time for each "frame". The interrupt fires and lights the LEDs in that frame. That interrupt then starts a new timer interrupt. When that one fires the LEDs are turned off. The CCP triggers and the whole thing starts all over again.

The thing works really well under a variety of disk speeds. I can cut power to the disk as as it slows, the display remains stable for a significant amout of time.

Issues that still remain.

1. Figure out what is causing that blasted noise. The numbers are taped to the disk so that I can move it about and change the design as needed. Once I have things working the way I want, I will either paste it down or go back to milling the numbers as I was doing before. I was just going though too much Lexan with milling when changing my mind on design so I went to printing the numbers and just taping them.

I think the other thing causing the noise is the spacers I have underneath to prevent light contamination from one slot to the next. Need to see if the disk is clicking on any of those.

2. Need to improve light incursion especially at the ends. I am wondering if I chose too wide of a LED angle. I am using 120 degrees. That was what was available. Don't know if there is anything narrower.

3. I might try playing with the post scaler to get a bit more sensitivity on positioning but it works pretty well now with no overflows so I might let it be.

4. Finally, get it to display something useful! I have an idea rolling around in my head of how that is going to work.

#### spinnaker

Joined Oct 29, 2009
7,835
Something useful. My birth date. An yes that is 1959 and not 1859! I still have a problem with light contamination. Not sure if I am going to be able to fix that problem. Perhaps a narrower LED angle?

I wonder if thicker stock would help? Right now the numbers are printed on paper. I plan to print on card stock and pain the back with black matte.

Code:
  frameMap[5].EDbankBits.led0 =1;  // 0
frameMap[0].EDbankBits.led1 =1;  // 6
frameMap[5].EDbankBits.led2 =1;  // /
frameMap[9].EDbankBits.led3 =1;  // 1
frameMap[2].EDbankBits.led4 =1;  // 5
frameMap[8].EDbankBits.led5 =1;  // /
frameMap[4].EDbankBits.led6 =1;  // 5
frameMap[9].EDbankBits.led7 =1;  // 9

Last edited: