COUNT command: possible accuracy issue?

kranenborg

Senior Member
Dear forum members

After some experimentation it seems to me that I cannot obtain the count accuracy as I should get according to the spec of the COUNT command. According to this spec, for a 64MHz 28X2 (with a very accurate crystal, see my recent Accurate Frequency Counter project) the following holds for the COUNT command:

* 400Khz max frequency for the incoming signal (2.5us)
* The unit of time for the sampling period used by the COUNT command: 62.5 us

So I fed a 400KHz signal to the count command with this sampling period. Since I had noticed a problem I also ran the count command with the frequency signal divided by 2 and by 4 (using a 74HC4040), and increased the sampling period to 125us and 250us respectively (to obtain the same number of counts). In all of these three cases the estimated frequency (= CountValue*divider/samplingperiod) should be really close to 400KHz. This is what I got as result as the calculated frequency based on the count value:

Divide: Sampl.period: CalcFreq (Khz):
1 (=400Khz) 62.5ms (=1x) 380.5
2 (=200KHz) 125ms (=2x) 398.7
4 (=100KHz) 250ms (=4x) 399.6

From this I think the following can be concluded:
  1. The upper limit frequency (for a single time unit) is likely much lower than 400KHz (as indicated by the first result but also by the second; even at 200KHz (divider 2) likely some signal transitions are missed.
  2. The effect of multiple time units (of 62.5us) used in a single sample period is probably very limited (maybe in the transition between time units a few counts are missed ...)

I checked the effect of DISCONNECT, it has some very minimal effect but certainly does not explain the large deviations in the first two cases.
For a test at 4MHz I got a similar result: with a divider of 16, resulting in an effective frequency of 250KHz fed to the counter (which is way lower than 400 KHz) and measurement time of 250ms I obtained an estimated frequency of 3.93Mhz instead of 4MHz, which falls in line with the results in the table

So how can we explain these results in the light of these specs:
  • Maybe the upper limit of 400KHz is incorrect and much lower?
  • Maybe some other processes interrupt the COUNT command, even within a single unit of time (62.5us at 64MHz), and if so, can they be disabled/controlled?
Does somebody know how the COUNT command is implemented, and does somebody have hints to get to the desired accuracy?

Thanks!
Jurjen Kranenborg
 

AllyCat

Senior Member
Hi,

I've never used X2s or the COUNT command, but I'm fairly sure that, like PULSOUT (and PAUSEUS), it is timed by precise Assembler code, exactly 10 PIC instructions (= 40 clock cycles) between Pulse integer counts, but 20 instructions between Edge-testing in the COUNT command (hence a minimum of 160 clock cycles for each count increment during the monitoring period). I don't know how the "window" period is controlled, but COUNT will always have a variation of +/- 1 count, depending on the delay between the start of the window and the first valid input edge (I doubt if the assembler code is clever enough to work with both edges).

I have always considered COUNT and PULSOUT to be "blocking" commands but in a thread a few months ago, I believe hippy said that it was not blocking because interrupts are NOT disabled. It surprised me that PULSOUT might not be "accurate", but I don't know which interrupts are normally active (particularly with X2s). However, the test for a new Program Download occurs BETWEEN instructions, so I wouldn't expect a DISCONNECT to make any difference. I do know that with M2s, the Timer 1 interrupt (every 20 ms), which is used for the "time" variable and Servo pulses, continues to run even if a DISABLETIME is executed.

As an alternative, I would look to some of the base PIC hardware modules, such as the "Timer 1 Gate" function which is available on all M2s (I don't know about X2s). Timer 1 normally increments at the PIC Instruction rate (i.e. 40 times faster than COUNT) but can accept an external input and can be gated by pin-hardware (perhaps driven from a PWM output), all controlled by SFR commands. You would need to check the "base PIC" data sheet for your X2, but I gave details of the M2 module in post #10 of this thread. Another possibility is the TOUCH hardware, which basically has an oscillator (that you could over-ride) and a gated period from an internal timer. But I believe the X2 Touch hardware is different to that in M2s, giving only a 10 bit result, unlike the M2's 16 bits.

Cheers, Alan.
 

kranenborg

Senior Member
Thanks Allycat, indeed trying to use the hardware modules as much as possible would likely be the best way to circumvent timing issues in the software implementation, and I will look further in that direction. Maybe - since the 28X2 has both Timer1 and Timer3 available - a setup is possible where one of them acts as a gated counter with the frequency-to-be-measured signal as gate input, and the other timer as a very accurate (driven by the crystal oscillator) fixed-length timer that stops the counter at a predefined time interval (also purely by hardware). This would then imply a hardware implementation of the COUNT command. Will dive into the PIC datasheet to see what is possible, I know my way around there ...

Maybe Technical would be willing to elaborate more on the practical accuracy of COUNT, PULSOUT as compared to the specs in my post (and maybe possible ways to improve on their accuracy towards the spec as well)

BTW: for those who need info on timer usage, the following link may be useful: Hardware timers utilization in the PICAXE
 

tmfkam

Senior Member
Hello.

Sometime back I wrote a PicAxe program that used the Count function to allow measurement of the frequency of the electricity supply to within a few milliHertz. I have since re-written that program for the base PIC processor, using another BASIC dialect. I was inspired by a MicroChip Compiled Tips 'N Tricks Guide PDF that was mentioned on a forum that I subscribe to.

Chapter 3, Section 1 describes how to use the CCP module to capture the rising and falling edges of a square wave, using a timer module to time the width of those pulses, and so calculate the frequency.

I'm sure if you read that article, the information will be valuable and allow you to achieve your goals.

For as much as it is worth, here's the code I used in my frequency meter as written in for the base PIC (it is not PicAxe BASIC and so won't compile for PicAxe). As the time taken to measure a nominal 50Hz period gets close to the timer overflow, I had to test to see if two concurrent overflow events had occurred before assuming that no signal was present. A 400kHz waveform would not require this double overflow test. Hopefully, you'll get the idea and be able to implement something similar in PicAxe BASIC.

Code:
InitTimer1 OSC, PS1_2              'Changed to PS1_2 for 250ns resolution

On Interrupt Timer1Overflow Call Overflowed

Sub GetFreq
    GetRising
    Let FreqTotal = Pulse_Width
    GetFalling
    Let FreqTotal = FreqTotal + Pulse_Width
End Sub

Sub GetRising

    Let Pulse_Width = 0
    Let OverFlow = 0
    'Configure CCP1  to Capture rising edge
    Let CCP1CON    = 5   '00000101
    StartTimer       1
    Let CCP1IF     = 0

    Do While CCP1IF = 0    'Wait for rising edge
    Loop

    Let TMR1H  = 0
    Let TMR1L  = 0   'Clear timer to zero
    Let OverFlow = 0

    CCP1IF  = 0      'Clear flag
    'Configure CCP1 to Capture Falling Edge
    CCP1CON = 4  '00000100'

    Do While CCP1IF = 0   'Wait for falling edge
    Loop

    StopTimer 1            'Stop the time
    Let Pulse_Width = Timer1   'Save the timer value
    Let CCP1IF = 0             'Clear the CCP1 flag

End Sub

Sub GetFalling

    Let Pulse_Width = 0
    Let OverFlow = 0
    'Configure CCP1  to Capture falling edge
    Let CCP1CON    = 4   '00000100

    StartTimer       1
    Let CCP1IF     = 0

    Do While CCP1IF = 0    'Wait for falling edge
    Loop

    Let TMR1H  = 0
    Let TMR1L  = 0   'Clear timer to zero
    Let OverFlow = 0

    CCP1IF = 0             'Clear flag
    'Configure CCP1 to Capture rising Edge
    CCP1CON = 5  '00000101'

    Do While CCP1IF = 0   'Wait for rising edge
    Loop

    StopTimer 1            'Stop the time
    Let Pulse_Width = Timer1   'Save the timer value
    Let CCP1IF = 0             'Clear the CCP1 flag

End Sub

Sub Overflowed
    Let OverFlow = OverFlow + 1
    If OverFlow > 1 Then
        Let CCP1IF   = 1
        Let OverFlow = 0
    End If
End Sub
Here's a link to the MicroChip PDF:
MicroChip Compiled Tips 'N Tricks
 

kranenborg

Senior Member
I took this issue up again by checking the behavior/accuracy of the COUNT command at its specced limit of 400KHz and lower (at 64MHz clock with an 28X2, see also the COUNT command definition for these limits).

I fed a 28X2 @ 64MHz (16 Mhz crystal) with several signals in the range of 400KHz and lower, and then subsequently calculated the relative error of the measured frequency (estimated from the COUNT command results) compared to the frequency of the signal fed to it (generated by another crystal-based 28X2, using a PWM signal). Since I had a 4MHz crystal at hand as well , I was able to do the same test at 4x lower frequencies for both the measurement ( 28X2 @ 16MHz) and the generated frequencies (100KHz and less). The below graphs gives the results (% relative error), with on the horizontal axis the frequency (Hz) of the fed signal:

RelErr_CountCommand.pngRelErr_CountCommand_100KHz.png

I feel that these pictures shows it quite clearly: the frequency limit of COUNT command at 64MHz (16MHz) is not 400KHz (100KHz) as stated in the documentation but is equal to approx. half of it instead, namely 200KHz (i.e. 5us signal width, not 2.5us) resp 50KHz . The second figure shows that this behaviour scales quite well with system clock frequency, so the conclusion of a twice as low maximum frequency compared to documentation likely holds for other processor frequencies as well.
I do not think this is a big limitation, but it is good to know the actual signal width (us) / frequency (Hz) limits in order to be able to use the COUNT command for really accurate measurements (and the results show one can!), and I hope this experiment serves that purpose.

Best regards,
Jurjen
https://www.kranenborg.org/electronics
 
Last edited:

AllyCat

Senior Member
Hi,

You haven't indicated what COUNT values you've actually measured, but I would expect some "dither" (due to asynchronous sampling/polling) unless the value is large. However, this post from hippy is the one I referred to in my previous post. But it's actually #5 from Buzby that perhaps provides a clue. Appendix 4 in Manual 4 says:

"Note that other timing commands (e.g. count, pulsin, pulsout etc.) do not disable the interrupts, but, if active, the hardware interrupt processing time may affect the accuracy of these commands when they are processed."

However, it's not clear which interrupts (if any) might be active by default. As I've said before, I only ever use M2s, so can't comment on the behaviour of X2s. It does seem strange that the error curve apparently has a "knee" (or corner) at 200 kHz.

Cheers, Alan.
 

kranenborg

Senior Member
Hi Alan,

In post #5 I added measurements of the behavior at a 4x lower measurement frequency and test frequency, showing fairly consistent behavior: the maximum frequency for accurate COUNT results seems to scale with the processor frequency but is a factor two smaller than according to documentation.

I have written the test program for this analysis as a variant of the Accurate Frequency Counter (0 - 65MHz) code. Goal was to obtain a large count value for the whole frequency range (50khZ - 400KhZ) , exactly for the reason that you wrote. It is attached in this post, but the essentials of the code are here, showing which COUNT command gets selected depending on frequency (note: "Div1InputPin is the signal input pin):

Code:
GetFrequency:

'Switch to high speed, accurate crystal oscillator (and let is settle)
DISCONNECT
SETFREQ em64

'Take an initial quick sample in one-twentieth of a second using the
'/2048 input.
Count Div2048InputPin,Sample50ms,CountW
FreqEst = CountW * 41

Select Case FreqEst

Case >262 '262 kHZ - 400 kHz
    Count Div1InputPin,Sample125ms,CountW

Case >131 '131 kHZ - 262 kHz
    Count Div1InputPin,Sample250ms,CountW

Case >65 '65 - 131 KHz'
    Count Div1InputPin,Sample500ms,CountW

Else '<= 65 KHz
    Count Div1InputPin,Sample1000ms,CountW

EndSelect
RECONNECT
Return
In this way COUNT values of above 32535 are generally obtained for the whole range of tested frequencies, allowing accurate measurement and estimation of the frequency.

I must admit that I myself are also at loss on the explanation of the error behavior (and the potential role of hardware interrupts), but at least these measurement results seem consistent enough to define a realistic baseline for the maximum COUNT signal frequency as a function of processor frequency --> 200KHz at 64MHz (28X2), 50KhZ at 16MHz

/Jurjen
 

Attachments

Last edited:

AllyCat

Senior Member
Hi,
It does seem strange that the error curve apparently has a "knee" (or corner) at 200 kHz.
I think I can "explain" what might be happening. Nothing to do with interrupts but a "bug" in the PICaxe interpreter code. They do occur occasionally (I've found 3 or 4) but if nobody else has found them in 10 years or more (since the M2s were released) they can't be too significant. Even the base PICs have a few, and I often wonder if one of them is the reason why the M2's HSERIN doesn't support "inverted" (Idle Low) data. ;)

I'll start at the beginning, the OP probably knows most of this, but others might be interested. The (base) PIC (into which the PICaxe interpreter is programmed during manufacture) divides the "headline" clock rate by 4 to give an (Assembler or Machine Code) "Instruction Cycle". For example, an Instruction such as: A = B + C might be divided into 4 (clock) cycles: 1) Read B from memory, 2) Read C from memory, 3) Add B and C to create A, 4) Write A to memory (note that A, B and C are not necessarily different locations). Personally, I always prefer to consider a (M2 default) clock frequency of 4 MHz, because it gives a simple 1 MHz Instruction Cycle (i.e. 1 us). All other clock frequencies can be scaled up (in frequency) or down (in period) from this, as appropriate. Nearly all the PIC instructions (or OpCodes) take one instruction cycle, the exception being the "Jumps" (i.e. GOTO / IF ..) which take two cycles, because a new "address" for the next instruction may also need to be calculated.

Even quite a simple PICaxe command uses about 500 base PIC "OpCodes", for example to decode the program "tokens" and because the PICaxe calculates internally with numerical Words (pairs of bytes), whilst the base PIC operates entirely with bytes. It's easier to first consider the PULSOUT command which has an "overhead" of hundreds of PIC instructions, similar to the other PICaxe instructions, in its lead-in (header) and lead-out (tail) code, which respectively start and stop the pulse. However, the "core" of these instructions is a tight loop of exactly 10 PIC instructions, which decrements a (software) counter and loops back until the count becomes zero. This counter has 16 bits, so the code needs to use separate (PIC) Op-Codes to first decrement the "Low" byte, and test if it is zero. If zero, it then decrements the "High" byte and tests that for zero (otherwise it "kills time", since the loop must always pass through exactly 10 x 1 us instruction cycles). Unless both bytes are zero, the program code Jumps backwards, which takes another 2 instruction cycles. I don't know if the longest path takes a full 10 instruction cycles, but if not it would be "padded" with one or more "NOP" (No OPeration) instructions (which are common to most microcontrollers).

Now, we can consider the COUNT command, which probably uses a similar software-timed method, but needs to maintain THREE software counters. The first (16-bit) counter obviously records the Count value, but two more may be needed to time the counting (acquisition) window. This window is selected in 1 ms intervals (up to 65.535 seconds) requiring a Word counter, but the "detection" loop will be faster than that, so a pre-scaler loop/counter would be required (probably only a single byte). These three counters (at least 5 bytes) can't all be maintained within a maximum of 10 instruction cycles, so the detection loop is presumably 20 instructions long. Furthermore, the "detector" needs to work within a half cycle of the input, because it must look for a change in the binary value, i.e. 1 to 0 or 0 to 1. Thus the full "Count" detection loop needs at least 40 instruction cycles for each increment, i.e. 40 us at the default clock frequency.

Suppose that the input signal toggles every 30 us and the "detection" loop polls it every 20 us. Then, if the toggle appears within the first 10 us of each pass (loop), then on the next pass the input level will not have changed so the code does not increment the "count" value. Thus for half of the passes the count is incremented and for the other half it is not. Assume that there is a "bug" such that the count path (when a toggle has occurred) takes 21 instruction cycles instead of the correct 20 cycles. Then, the average polling interval will be 20.5 cycles, or 2.5% slow, which is what you appear to have observed. If the input toggles every 20 (or 21) us then every time the input is polled the level will have changed, so the code always executes the "count" path and the time will be 5% slow. Conversely, when the toggling period exceeds 40 (or 41) us, sometimes there will be two consecutive loops when no count increment occurs and here you have observed that there is "no error".

Thus, it appears that the (assumed) bug is not simply that the "count increment" path takes 21 instruction cycles instead of 20, but that there is some strange interaction (which adds an instruction cycle) between the "toggled" and "toggled in the previous pass" loops, that does NOT occur if a "toggled two or more passes ago" is involved (even though plenty of "toggled" passes still must be occurring). I'm not sure if anything more could be learned by testing above the "official" maximum speed (obviously some toggles/counts will be missed) or with pulses that have a non-equal mark-space ratio. But probably it won't help fix the bug, if it does exist. :(

Cheers, Alan.
 
Top