@Martin,
Well, I don’t really use Github as a blog for discussions. Maybe this should instead be moved to the software related section of the EEVblog? No idea how to do that apart from creating a new topic.
In the mean time and just to put minds at rest... The comment you quote is true but only applies in the range below 2Hz. Let me explain:
The basic restriction is that the PIC is edge triggered. You can select whether you want the rising or falling edge initially during setup but that is it. The original frequency counter simply counted the number of edges within the gate time (e.g. 1s). For RPM measurements that is way to coarse, so I implemented a reciprocal measurement of the period at low frequencies. In that mode, my software needs to see at least 2 (say falling) edges = 1 period within the measurement time which is 1 second as the longest gate time used for very low frequencies. That condition is only guaranteed if the frequency is >= 2Hz, hence at frequencies below 2Hz to 1Hz the display flips between showing the correct value, e.g. 1.23Hz and 1Hz, depending if 1 or 2 falling edges were detected within the measurement period. The closer you get to 1Hz the more likely is it that you just see 1 instead of 1.xx. From 1Hz and below you just see zero. This is what the comment meant. It starts working from 1Hz onward but for a fully steady display you need 2Hz or more.
If the frequency is above 1Hz and below 200Hz, reciprocal measurement is used where my software measures the time from the start of the first period all the way to end of the last full period within the measurement period, and counts the number of full periods, it saw. Example, at 50.123Hz, a period is 19.95ms and 49 full periods fit into 1 second, So the total accumulated time measured would be 0.9775s. 49 would then be divided by this to get the frequency. Crucially these are not 49 separate measurements where each could be affected by missing an edge. Instead this is is one continuous measurement period and all edges are detected.
In reality, the time within this software is measured in 20us increments and is stored as a 16bit integer. So 0.9775s equates to the value of 48879. This simplification introduces an error of up to 20us. The poor 8-bit PIC is then doing a 32-bit multiplication of 49 * 50000*1000 and then divides that huge number in another 32-bit operation by 48879 to get the result of 50123. This is the frequency in milliHertz which is then rounded to 2 decimals and shown as 50.12Hz. Should the PIC see only 48 instead of 49 full periods, because of just missing the first edge, it simply uses the values for 48 periods and comes to the same result. Its slightly more complex than that but in principle this is how things work.
This algorithm works up to 255Hz but I decided to draw the line at 200Hz and switch back to the original mode, because in reciprocal mode the accuracy gets rapidly worse the higher the frequency because the 20us is just too coarse for measuring smaller periods. Sadly changing the 20us inherited from the original firmware without a complete redesign of the software is not possible.
I never considered extending the counter the other way, i.e. below 1Hz operation. The only way to do that would be to extend the gate time / measurement time. That would be fairly easy but having to wait 10 or 100s for each display update seems not very useful.
Maybe we can reuse some of your effort, so let me put my thoughts step by step:
- If you measure e.g. 1000.0 Hz for one second (i.e. 50000 ticks of 20 µs) then you will get a period length of 1000 µs and 999 full periods.
Total accumulated period time is 999000 µs, giving 49950 ticks, the reminder to the 50000 ticks (=1 second) is 50 ticks.
...
So the reminder to the full 1 second period of 50000 ticks increases with increasing sub-Hz frequency.
IIRC this 499xx value (the full period tick sum) is always available even for higher frequencies.
; Note 2: This method could create a pcnt that can get too high for the subsequent 32-bit maths
; Therefore there is a check to stop accumulating periods and incrementing pcnt if
; 80 (in frequency mode) or 140 (in RPM) mode is reached
...
;
; check if pcnt has reached maximum. If yes, don't add amy further periods
;
...
goto P_done ; [64+1] stop if pcnt >= 80 (freq) or >= 140 (rpm)
I understand what you mean and agree that this would be great, but unfortunately The PLJ-8LED needs a 13 MHz clock, so a simple 10 MHz GPSDO wont be any good. Also, simple GPS-derived frequency standards don't actually adjust a local oscillator, instead they contain a programmable divider that is normally set to produce the 1Hz aka 1PPS signal. Popular GPS chips like the NEO 7N for example allow you to set the divider to anything you like to change the 1PPS signal to 10 MHz for example. You can also set it to 13 MHz although that is actually outside the spec. The problem is that in the case of the NEO 7N (and probably similar chips) that frequency is full of jitter. If you set it to 10 MHz, you get a precise 10 million pulses in exactly 1 second, in other words 10 MHz, but the width of each pulse is not a constant 50ns, instead some are only 41ns and others 62ns.
This happens only at some frequencies, unfortunately 10MHz (and 13 MHz) are such frequencies. I made an investigation about this here: Those GPS-based references are still very useful to verify the accuracy of counters. A properly aligned counter should show exactly 10.000.000 because it gets exactly 10 million pulses per second. The fact that the pulses have different length does not matter. However, I don't think using such a signal as a clock would be very good. The alternative would be a proper GPSDO that uses a PLL to adjust a local TCXO. The mini GPS ref. clock from Leo Bodnar is such a thing that can produce a very accurate and clean signal from 400Hz to 800 MHz. (But with lots iof jitter, Ive heard this a lot but didn't know how bad it would be for my own applications. Not infrequently this issue comes up for me where I could do something that I want or need ONLY if I could generate this (insert stable time clock signal frequency here, its almost always ithin the range 9-28 MHz. )
Also, simple GPS-derived frequency standards don't actually adjust a local oscillator, instead they contain a programmable divider that is normally set to produce the 1Hz aka 1PPS signal. Popular GPS chips like the NEO 7N for example allow you to set the divider to anything you like to change the 1PPS signal to 10 MHz for example. You can also set it to 13 MHz although that is actually outside the spec. The problem is that in the case of the NEO 7N (and probably similar chips) that frequency is full of jitter. If you set it to 10 MHz, you get a precise 10 million pulses in exactly 1 second, in other words 10 MHz, but the width of each pulse is not a constant 50ns, instead some are only 41ns and others 62ns. This happens only at some frequencies, unfortunately 10MHz (and 13 MHz) are such frequencies. ...
It only generates 'reasonable' non horrible jitter at the following frequency steps. 12, 8, 6, 4.8, 6, 3, 2.4, 2, 1 MHz which are all integer divisible from 24MHz. U-Center only allows whole number frequency steps to be sent to the device so it is limiting for higher frequencies without jitter. **Full Bandwidth shown on the Micsig with all the other gear and the screen is 20M filtered.
#include <stdint.h> /* For uint8_t definition */
#include <stdbool.h> /* For true/false definition */
// display data buffer
unsigned char digits[5]={0};
// digit drive table (left=0 to right=4)
unsigned char digit_drive[5]={0x07, 0x0B, 0x0E,0x0D,0x0F};
// digit drive table (right=0 to left=4)
// unsigned char digit_drive[5]={0x0F,0x0D,0x0E,0x0B,0x07};
// bitmask for segment A , etc ..
#define _A 0x40
#define _B 0x80
#define _C 0x04
#define _D 0x01
#define _E 0x08
#define _F 0x10
#define _G 0x20
#define _DP 0x02
#define BLANK_PATTERN 0
// blank display pattern (7-segment code)
//translate to seven-segments
unsigned char symb[] =
{
(_A+_B+_C+_D+_E+_F ), // ABCDEF. = '0' ( # 0 )
( _B+_C ), // .BC.... = '1' ( # 1 )
(_A+_B +_D+_E +_G), // AB.DE.G = '2' ( # 2 )
(_A+_B+_C+_D +_G), // ABCD..G = '3' ( # 3 )
( _B+_C +_F+_G), // .BC..FG = '4' ( # 4 )
(_A +_C+_D +_F+_G), // A.CD.FG = '5' ( # 5 )
(_A +_C+_D+_E+_F+_G), // A.CDEFG = '6' ( # 6 )
(_A+_B+_C ), // ABC.... = '7' ( # 7 )
(_A+_B+_C+_D+_E+_F+_G), // ABCDEFG = '8' ( # 8 )
(_A+_B+_C+_D +_F+_G), // ABCD.FG = '9' ( # 9 )
(_A+_B+_C +_E+_F+_G), // ABC.EFG = 'A' ( # 10 )
( _C+_D+_E+_F+_G), // ..CDEFG = 'b' ( # 11 )
( _D+_E +_G), // ...DE.G = 'c' ( # 12 )
( _B+_C+_D+_E +_G), // .BCDE.G = 'd' ( # 13 )
(_A +_D+_E+_F+_G), // A..DEFG = 'E' ( # 14 )
(_A +_E+_F+_G), // A...EFG = 'F' ( # 15 )
(_A +_C+_D+_E+_F ), // A.CDEF. = 'G' ( # 16 )
( _B+_C +_E+_F+_G), // .BC.EFG = 'H' ( # 17 )
( _E ), // ....E.. = 'i' ( # 18 )
(BLANK_PATTERN ), // ....... = ' ' ( # 19 )
(0xFF ), // all segments on ( # 20 )
(_A+_B +_E+_F+_G), // AB..EFG = 'P' ( # 21 )
( _E +_G), // ....E.G = 'r' ( # 22 )
( _C+_D+_E +_G), // ..CDE.G = 'o' ( # 23 )
(_A+_B+_C +_F+_G), // ABC..FG = 'Q' ( # 24 )
( _C+_D+_E ), // ..CDE.. = 'u' ( # 25 )
( _D+_E+_F+_G), // ...DEFG = 't' ( # 26 )
(_A +_C+_D +_F+_G), // A.CD.FG = 'S' ( # 27 )
(_A+_B +_D+_E +_G), // AB.DE.G = 'Z' ( # 28 )
( _E+_F ), // ....EF. = 'I' ( # 29 )
( _B+_C+_D ), // .BCD.. = 'J' ( # 30 )
( _D+_E+_F+_G), // ...DEFG = 'k' ( # 31 )
( _D+_E+_F ), // ...DEF. = 'L' ( # 32 )
(_A+_B+_C +_E+_F ), // ABC.EF. = 'N' ( # 33 )
( _C+_D+_E+_F ), // ..CDEF. = 'V' ( # 34 )
( _D +_G), // ...D..G = '=' ( # 35 )
};
/******************************************************************************/
/* Main Program */
/******************************************************************************/
#define _XTAL_FREQ 20000000
#include <xc.h>
unsigned int counter=0;
// separate out the digits of "counter" into display buffer
void update_display(void) {
digits[4] = counter%10;
digits[3] = (counter/10)%10;
digits[2] = (counter/100)%10;
digits[1] = (counter/1000)%10;
digits[0] = counter/10000;
}
void main(void) {
TRISB = 0; //RB as Output
PORTB = 2; //turn on DP
TRISA = 0; //RA as Output
PORTA = 0x0F; //turn on 5th (rightmost) digit
__delay_ms(1000); //test display/DP
unsigned char i,j,k;
// display test, all segments
PORTB = 0xFF; //all segments on
for (j=0; j<5; j++) {
PORTA = digit_drive[j];
__delay_ms(400);
}
PORTB = 0;
while(1) //count up and display result
{
update_display();
for (k=0; k<50; k++) {
for (j=0; j<5; j++) {
PORTA = digit_drive[j];
PORTB = symb[digits[j]];
__delay_ms(1);
}
}
counter++;
}
}
Note: exposure caught the display while the last digit was changing.
I still think it needs a proper preamp...I don't like the idea of feeding an unknown signal directly to the PIC pin.
I still think it needs a proper preamp...I don't like the idea of feeding an unknown signal directly to the PIC pin.
There's one sketched out in this PDF:
http://www.iw1axr.eu/radio/BiTx/Frequenzimetro%20a%20led%20con%2016F628.pdf
(see pages 4 and 6 and 8 )