Can MPLABX simulator count processor cycles?

Status
Not open for further replies.
BINGO!!! Nice catch. I owe you a beer for that.
I guess I never caught that before, because it would probably exit the previous run with the correct value of zero. Anyway, I added an instruction to clear that register on entry, adjusted the cycle count, and now everything is correct.
 
I'm using the method described in Microchip appnote AN592. A digital I/O pin is connected to the Tmr0 input pin. Setting the digital pin to input (Hi-Z) enables the timer, and and setting it to an output disables it. Toggling the I/O pin while in output mode flushes the prescaler into the Tmr0 register, in order to determine it's value.

Here is the working counter.

The top line shows, from left to right, the 1ms count, the number of averaged samples in the running average buffer (described below), and the input signal duty cycle. The bottom line shows the 1 second count. The way it's set up, it starts out in 1 ms sample mode and displays an averaged value on the bottom line starting with 16 samples, then 32, 64 and finally 128 samples. Once it gets 128 continuous samples at constant input frequency it automatically switches to 1 second sampling and displays the one second count on the bottom line. It also does 1 ms samples between each 1 s sample to update the top count and detect frequency changes. This seems to work well at responding quickly to frequency changes, but giving maximum resolution when the frequency is stable.
Here is the counter in 1 ms averaging mode:

In this mode, only two decimal places are displayed. Even with 128 samples at 1 ms, the last digit tends to vary by ±2 counts.
 
Ah... Ok... You can do the same thing with just the RA2 (T0CKI) pin. That is, make it an 'input' for gate "on", make it an 'output' for gate "off", and clock it to flush and capture the prescaler value. Whether you're using one pin or two pins, the important thing to note is that you have a way to turn the gate "off" in a single cycle during that left over instruction cycle when we fall out of the gate timing loop.

That's really a very nice looking project, Bob.

Just for fun, and if you're interested... here's an example counter routine using a "cycle accurate" delay. There are two entry points. One for a 1-millisecond gate and one for a 1-second gate. The gate loop runs at 1-millisecond intervals using a delay value of 1-msec minus the 14 cycle loop overhead. With your 4-MHz clock, the gate is turned off precisely 1000 cycles (1-mS gate) or 1000000 cycles (1-sec gate) after being turned on.

Code:
;~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
;                                                                 ~
;~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
        radix   dec
go1000
        movlw   high(1000)+1    ; 1-second gate                   |00
        movwf   r3              ;                                 |00
        movlw   low(1000)       ;                                 |00
        movwf   r2              ;                                 |00
        goto    prep            ; branch unconditionally          |00
;~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
;                                                                 ~
;~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
        radix   dec
go0001
        movlw   high(1)+1       ; 1-msec gate                     |00
        movwf   r3              ;                                 |00
        movlw   low(1)          ;                                 |00
        movwf   r2              ;                                 |00
prep
        clrf    TMR0            ; clear TMR0 and prescaler        |00
        clrf    acc0            ; clear the 32-bit counter        |00
        clrf    acc1            ;  "                              |00
        clrf    acc2            ;  "                              |00
        clrf    acc3            ;  "                              |00
        bcf     STATUS,IRP      ; set FSR/INDF for bank 0-1       |00
        movlw   TRISA           ; setup indirect access to TRISA  |00
        movwf   FSR             ; for T0CKI (RA2) data direction  |00
        bsf     INDF,TRISA2     ; TRISA2 = 1, RA2 gate "on"       |00 (gate on)
gate
        inDelay(1*msecs-14)     ; 1-msec minus 14 (loop) cycles   |00       
        movlw   1<<TMR0IF       ; check TMR0 overflow each loop   |00 ( 1)
        andwf   INTCON,W        ;  "                              |00 ( 2)
        skpz                    ; TMR0 overflow? no, skip, else   |00 ( 3)
        bcf     INTCON,TMR0IF   ; clear TMR0IF flag               |00 ( 4)
        skpz                    ; TMR0 overflow? no, skip, else   |00 ( 5)
        movlw   1               ; bump the overflow counters      |00 ( 6)
        addwf   acc2,F          ; add 0 or 1                      |00 ( 7)
        skpnz                   ; zero result? no, skip, else     |00 ( 8)
        addwf   acc3,F          ; add 0 or 1                      |00 ( 9)
        decf    r2,F            ; decrement loop counter          |00 (10)
        skpnz                   ;  "                              |00 (11)
        decfsz  r3,F            ; done? yes, skip, else           |00 (12)
        goto    gate            ; do another 1-msec loop          |00 (14)(13)
        bcf     INDF,TRISA2     ; TRISA2 = 0, RA2 gate "off"      |00 (gate off)
;
;  perform a final check for TMR0 overflow
;
        movlw   0               ;                                 |00
        btfsc   INTCON,TMR0IF   ; TMR0 overflow? no, skip, else   |00
        movlw   1               ; bump the overflow counters      |00
        addwf   acc2,F          ; add 0 or 1                      |00
        skpnz                   ; zero? no, skip, else            |00
        addwf   acc3,F          ; add 0 or 1                      |00
        bcf     INTCON,TMR0IF   ; clear TMR0IF unconditionally    |00
;
;  transfer TMR0 and prescaler values into 32-bit count & return
;
        movf    TMR0,W          ;                                 |00
        movwf   acc1            ; xfer TMR0 value into count      |00
flush   bcf     PORTA,RA2       ; clock the T0CKI pin             |00
        bsf     PORTA,RA2       ;  "                              |00
        decf    acc0,F          ; decrement counter LSB           |00
        movf    TMR0,W          ; prescaler overflow into TMR0?   |00
        xorwf   acc1,W          ;  "                              |00
        bz      flush           ; no, loop (clock it again)       |00
        return                  ;                                 |00
;~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
For this example I used a simple "cycle accurate" in-line delay macro that doesn't use any variables. It supports a 0-1027 cycle range to match your requirements (delays no longer than 1000 cycles) and it generates 4, 5, or 6 instructions per macro call.

Have fun. Cheerful regards. Mike
Code:
;******************************************************************
;  K8LH inDelay(0..1027 cycle), in-line fixed delay, 0 bytes RAM  *
;******************************************************************
        radix dec
clock   equ     4               ; 4, 8, 12, 16, 20 MHz, etc.
usecs   equ     clock/4         ; cycles/usec operand multiplier
msecs   equ     clock/4*1000    ; cycles/msec operand multiplier

inDelay macro   delay           ; 0..1027 cycle range
     if (delay > 1027)
        error "inDelay range error"
     endif
        local   loop
     if delay > 3
        movlw   delay/4
loop    addlw   -1              ; 4 cycle loop (14 bit core)
        skpz                    ; done? yes, skip, else
        goto    loop            ; loop (do another 4 cycles)
     endif
     if delay&2                 ; delay%4 == 2 or delay%4 == 3
        goto    $+1             ; delay 2 additional cycles
     endif
     if delay&1                 ; delay%4 == 1 or delay%4 == 3
        nop                     ; delay 1 additional cycle
     endif
        endm
;******************************************************************
 
Last edited:
It's good to know that it can be done with just the RA2 pin. It had crossed my mind at one point that it might be possible to use a single I/O pin, but I'd never seen it done anywhere, and I had lots of pins available at the time. As the project progressed, and feature creep set in, having more I/O would have been useful. I'll need to redesign the PCB now anyway, because when I added an analog input, I had to do some surgery on the traces.
I'll probably replace my old 1 ms delay routine with the one you posted earlier, or some variation of it. It's a lot more compact and versatile, and I'll be able to replace several other delay routines that are used by the LCD display.
I was thinking about re-doing my 50-MHz counter with an OLED display on an 8-pin PIC
FYI, my very first frequency counter project had a 3 1/2 digit Nixie display, and used just two chips: an 8-pin 12F629 and an Allegro A6818 32 bit serial-in parallel out HV driver.
 
Interesting project. I'm undecided about the idea of using a GPS module for the gate pulse though. The long term accuracy would be excellent of course, but if my calculations are correct, the jitter for a single gate pulse could cause an error of ±1 count (or more, if uncompensated) at 100 MHz, which admittedly is a small price to pay in order to avoid the drift of a crystal oscillator.

In fact, I had been thinking ahead to a future frequency counter project that could go above the 50 MHz limit of the PIC's Timer_0. I figured, why stop at 100 MHz which is only double the basic 50 MHz limit. So, I bought a 1.1 GHz prescaler IC, MC12080DG to do the heavy lifting (I haven't hooked it up yet). The idea would be to use exactly the same technique as the current project, to flush both the internal prescaler and the MC12080 by toggling a PIC output bit, so that the addition of the external prescaler wouldn't decrease the count resolution.
 
Interesting project. I'm undecided about the idea of using a GPS module for the gate pulse though.
I'm not sure I would want to use a GPS module, either. However, the more I think about it, the more I like the idea of using this counter IC.

Here's a quick-n'-dirty concept drawing;

 
Last edited:
In fact, I had been thinking ahead to a future frequency counter project that could go above the 50 MHz limit of the PIC's Timer_0. I figured, why stop at 100 MHz

Just use an ESP32 module - program with an arduino IDE. It runs at 240MHz, counts microseconds for you and you can easily output to a nice I2C or SPI OLED display.

A couple of on-board touch sensor pins can be used to set manual resolution if you need to break from an autorange in software.
 
Status
Not open for further replies.
Cookies are required to use this site. You must accept them to continue using the site. Learn more…