Continue to Site

Welcome to our site!

Electro Tech is an online community (with over 170,000 members) who enjoy talking about and building electronic circuits, projects and gadgets. To participate you need to register. Registration is free. Click here to register now.

  • Welcome to our site! Electro Tech is an online community (with over 170,000 members) who enjoy talking about and building electronic circuits, projects and gadgets. To participate you need to register. Registration is free. Click here to register now.

Voltage to frequency converter with PIC

Status
Not open for further replies.

Andy_123

Member
Hello guys,

I am looking for PIC implementation for voltage to frequency converter (VFC).
Program must generate square wave signal with equal time ON/OFF based on the value from analog input.
10V = about 50KHz
0V = OFF
10mV= 50Hz (lovest resolution of the input)

Linearity and repeatability is important, max frequency can be 30KHz and higher.

Response time can be up to 0.5 sec, lower is better.

I have available PICs to try: 12F675, 16F628, 16F670, but can obtain any other, if necesary.

Actually my task is a little more complicated, but if I find this, I'll ask the rest.

Thanks
 
If you use a voltage divider to make the 10V down to 5V, you can use PIC with 10bit AD.
10bit has 1024 variance, 0=0hz, 1=50 Hz, 2=100 Hz 1023= 51150 Hz/51.15kHz.
If you have Picbasic, Picbasic have FREQOUT command, FREQOUT Pin , Period , Freq.
I think one of the disadvantages of Picbasic is, it wont start a new AD reading until its finish the freqout routine.
Code:
FREQOUT PORTA.0, 100,1000
makes on PORTA bit0 for 100ms 1000hz.
Possible in assembler you can make you own timing to have constant freq on the pin you want, and initiate the AD in your delay cycle.

In case if you already know all of this, then what is you question :?:


I hope this help.

STEVE
 
Thank s for the idea.
5V is not a problem - I have active absolute value circuit involved anyway, so I just change gain value.

FREQOUT will be a problem.
Technically I need to do a couple more things while generatin pulse train.
One of them is to generate 3 more pulse trains of the same frequency with 90 deg phase shift each.
I probably need some kind of timer interrupt routine or so.

In case if you already know all of this, then what is you question
I am just looking for ideas and examples.

This circuit is working for a while with descrete ICs with total cost over $25. (VFC320 chip)
I am sure I can make it with PIC for much less with less ICs involved.
 
Perhaps you had better let us know what you are trying to do?, there may be a simpler way?.

Certainly if you are introducing lots more requirements you are likely to start running short of cycles.
 
Nigel

This device is incremental encoder simulator.

I think PIC should be fast enough to handle this.

One addtional requirement is to switch phase of outputs 2 and 4 if another descrete input is ON

IN=off - seq 0-90-180-270
IN=ON - seq 0-270-180-90

That is it, no more features.
 
An incremental encoder normally has two outputs with the ff. sequence

output1 - 0110011001100110.....
output2 - 0011001100110011.....

When a switch is ON, the sequence is reversed

output1 - 0011001100110011.....
output2 - 0110011001100110.....

In addition, you want the encoder to step through the sequence at a rate adjustable from 0Hz to 50Khz in 50Hz steps. The adjustment is through an an analog voltage input. Is this what you need?

This is doable except that at the highest frequecies, the speed adjustment may not be smooth. That is, changing the speed from 49050Hz to 50000Hz might not be easily done.
 
Motion,
This is exactly what I need.
Thank you for making my idea clear.


The only minor addition to these 2 pulse trains - I want to have 2 more trains with "NOT" value for two main channels.

Like I said: I have this device working using very expensive VFC 320, couple triggers and XOR ICs.
I just want to simplify the design.

If this works, next step will be to simulate SSI interface encoder, but this is a future project.
 
Let me outline the solution I have in mind. There are two parts to the program. The first part is generating the encoder sequence at a set frequency. The second part is reading from an analog input, perform A/D conversion, and translating the result to a frequency setting used in the first part.

The way to generate the sequence is through interrupts. 50Khz is a relatively high rate and a 20Mhz PIC16F628 should give you enough headroom. The ff. steps are needed to setup:

a. Setup CCP1CON register for "compare mode, trigger special event". I.E., initialize CCP1CON to 00001011B.
b. Initialize TMR1CON with the value 00010001 (TMR1ON and 1:2 prescale). This will supply a 2.5Mhz clock to TMR1 from a 20Mhz oscillator.
c. Initialize CCPR1H,CCPR1L with the value 50. This will give an initial interrupt rate of 50KHz. To get an interrupt rate of 50Hz, the registers should be intialized to 50. To get a specific frequency, a suitable value from 50-50,000 should be entered. Notice there are 1000 values. A lookup table is best to achieve a linear relationship to the analog input. However, this exceeds the capacity of the PIC. You may have to limit the table to 500 items and interpolate in-between values.
d. Enable interrupts for the CCP1 by setting CCP1IE bit in PIE1 register. When everthing is all setup, enable interrupts by setting GIE and PEIE in the INTCON register.

The following is a sample interrupt service routine:

Code:
;=======================================
         ORG      4

INT_SERVE:                             ; 2
         MOVWF    TEMP_W
         SWAPF    STATUS,W
         CLRF     STATUS         ; Select RAM BANK0
         MOVWF    TEMP_S
         MOVF     PCLATH,W
         MOVWF    TPCLATH
         CLRF     PCLATH
;--------------------------------------------------
         BTFSC    SELECT_PIN
         INCF     PHASE_CNT,F
         BTFSS    SELECT_PIN
         DECF     PHASE_CNT,F
;
         CALL     GET_PHASE            ; 8
         MOVWF    PORTB
;--------------------------------------------------
UPD_TIMER:
         BTFSS    UPD_TMR_VAL
         GOTO     UPD_TIMER_END
         BCF      UPD_TMR_VAL
;
         MOVF     TMR_VAL,W
         MOVWF    CCPR1L
         MOVF     TMR_VAL+1,W
         MOVWF    CCPR1H
UPD_TIMER_END:
;--------------------------------------------------
INT_SERVE_END:
         BCF      PIR1,CCP1IF
;
         MOVF     TPCLATH,W
         MOVWF    PCLATH
         SWAPF    TEMP_S,W
         MOVWF    STATUS
         SWAPF    TEMP_W,F
         SWAPF    TEMP_W,W
;
         RETFIE                        ; 2
;=======================================
GET_PHASE:
         MOVF     PHASE_CNT,W
         ANDLW    B'00000011'
         ADDWF    PCL,F
         RETLW    B'00000101'
         RETLW    B'00001001'
         RETLW    B'00001010'
         RETLW    B'00000110'
;--------------------------------------------------

The ISR uses 38 instruction cycles or 7.6usec. At 50Khz interrupt rate, there are 100 or 20usec instruction steps between interrupts. This gives a utilization of 38% of CPU cycles for pulse generation.
 
Motion:
Thanks for great idea, I think this is exactly what I was looking for.
I never used a timer interrupt, and had some filling that I must use it.

I have to look at the lookup table, 500 values may not be enough.
I will look at 2 different ways: use a formula or get a higher capacity PIC.

I will try it in the next few days and let you know.
 
Just a quick update:
I was going over the program last night - I think it will do the job, I will modify interruput routie a little and add analog input readings.

Questions for far:

- Timer value updated only at the interrupt routine (ccpr1l, ccpr1h):
Can this be done in the main program?
I see the reaction time issue at the low speeds and at speed "0"

- What is a max value in ccpr1l? 2^16-1= 65535?
If yes I will need to set only CCPR1L for all dividers leaving CCPR1H=0.
This will reduce a lookup table size from 2000 to 1000 values.

Thanks
 
Andy_123 said:
Just a quick update:
I was going over the program last night - I think it will do the job, I will modify interruput routie a little and add analog input readings.

I would recommend to add the analog input readings on the main program instead of the ISR. The interrupts occur at varying rate. The A/D conversion is completed asynchronous to this.

I would also recommend to average the analog readings over several samples to reduce the effect of noise. This is possible because you can make up to 50K samples/sec.

Let me answer the second question first.

- What is a max value in ccpr1l? 2^16-1= 65535?
If yes I will need to set only CCPR1L for all dividers leaving CCPR1H=0.
This will reduce a lookup table size from 2000 to 1000 values.

The maximum value is 65535. If CCPR1H is fixed at 0, the maximum value of the pair becomes 255. Therefore the minimum frequency is 2,500,000/255=9.8Khz. To generate 50Hz, you would need a value of 50,000 or CCPR1H=0C3h, CCPR1L=50h.

- Timer value updated only at the interrupt routine (ccpr1l, ccpr1h):
Can this be done in the main program?
I see the reaction time issue at the low speeds and at speed "0"

If you write to CCPR1 in the main program, an interrupt can occur while writing to one register only (partial update of CCPR1 registers). Writing within the interrupt service routine guarantees both registers can be updated before the next interrupt occurs.

I understand at low speed, it will take longer before CCPR1 is written. However, rapidly changing CCPR1 will not speed up the response because the value of CCPR1 is only used when a match with TMR1 occurs.

The frequency divider method works from 50Hz-50Khz speeds. At speed 0, the TMR1 can be turned OFF and turned back ON when the speed is non-zero. Even at 50Hz, the update time is 0.02sec which is faster than your requirement.
 
Project is on hold today: nice warm day outside - spring clean-up time 8)

I would recommend to add the analog input readings on the main program instead of the ISR
Yes, this is what I was planning to do, I just did not worded it correctly.
Analog goes to the main routine.

The maximum value is 65535. If CCPR1H is fixed at 0, the maximum value of the pair becomes 255. Therefore the minimum frequency is 2,500,000/255=9.8Khz. To generate 50Hz, you would need a value of 50,000 or CCPR1H=0C3h, CCPR1L=50h.
I did not realise that CCPR1H and CCPR1L are 8 bit each - my mistake, I will be using both.

I see the reason why you are not updating timer values in the main program, I agree.

One more thing I am not sure:
It takes 4 interrupts to generate a complete sequence, right?
With max interrupt rate of 50kHz we can generate only 12.5Khz stream not 50Khz.
If this is correct what will be the best way to correct it: change value in CCPR1?
 
It takes 4 interrupts to generate a complete sequence, right?
With max interrupt rate of 50kHz we can generate only 12.5Khz stream not 50Khz.
If this is correct what will be the best way to correct it: change value in CCPR1?

If you change the prescale value of TMR1 from 1:2 to 1:1, you could double the speed. This will increase the CPU utilization to 80%. Quadrupling the frequency would push it beyond the PIC16F628's capability.

You may change to a PIC18F242. The speed of the chip is double that of the PIC16F628. In addition to the increased storage for the look-up table, it has the TABLRD instruction to allow reading of the entire 16-bit program memory word. Added to that, optimizations with context saving will reduce ISR service time and CPU utilization.
 
Update:
I just completed testing of interrupt routine without analog portion.
Everything works as expected:
I tested with 16F628 and 20 Mhz oscillator.
With divider 50 (32hex) it generates 4 pulse trains at 12.5MHz as expected - 80 usec period - measurements with 2 channel oscilloscope.

Also tested 3 other dividers: 500, 5000 and 50000 everythig works fine.

Now before I add analog reading, I need to see how I can reduce interrupt routine to minimize CPU usage, so I can increase frequency
I really need at least 33kHz (50kHz was my goal)
I will be ordering 18F242 as recommended, but I hope I can use 16F628.

Any ideas how can I trim interrupt routine? Do I really need to save STATUS and PCLATH?

Edit: just ralized that 16F628 does not have analog inputs :roll:
Will try to move to 16F870 until 242 arrives

Thanks for help!!!
 
Motion:
Do you have any interrupt handling examples for 18F242 similar to one above for 16F628?

18F242 is much more powerfull and I see that I can use some extended commands to reduce
execution time, espcially fast stack operations, and table handling.

I am expecting 18F242 in a few days, so I need program change.

Thanks.
 
The following is the code modified for the PIC18F242. The ISR uses the fast return stack for context saving and so a lot of code has been eliminated. The phase signal generation has also been optimized. The ISR is now down to 18 instruction cyles or at 40Mhz = 1.8 usec. The interrupt rate can go as high as 500khz!

Code:
;======================================= 
         ORG      8 

INT_SERVE:                              
         MOVLW    80h
         ADDWF    PHASE_CNT,F 
; 
         BTFSC    SELECT_PIN
         RLCF     PORTB,F           ; All 8 bits must be used as outputs!
         BTFSS    SELECT_PIN
         RRCF     PORTB,F           ; All 8 bits must be used as outputs!
;-------------------------------------------------- 
UPD_TIMER: 
         BTFSS    UPD_TMR_VAL 
         BRA      UPD_TIMER_END 
         BCF      UPD_TMR_VAL 
; 
         MOVFF    TMR_VAL,CCPR1L 
         MOVFF    TMR_VAL+1,CCPR1H 
UPD_TIMER_END: 
;-------------------------------------------------- 
INT_SERVE_END: 
         BCF      PIR1,CCP1IF 
; 
         RETFIE   FAST          ; Use fast return stack         
;=======================================

Once everything is done, could you post the finished code so everyone can appreciate it?
 
Thanks,

I will definately post final code.
18F242 will be here Friday, so I hope to get some results over weekend.

Any additional configuration registers I need to set?
Can we make routine shorter by updating timer values every interrupt even no change in value ?
These 3 instructions will be gone:
BTFSS UPD_TMR_VAL
BRA UPD_TIMER_END
BCF UPD_TMR_VAL

Will interrupt automatically save stack or I need to execute
CALL <adr>, fast ?

I really like the idea to use RLCF and RRCF instead of table !!!
 
Upon further review, there is a small bug in the code that I had posted. The corrected one is as follows:

Code:
;======================================= 
         ORG      8 

INT_SERVE:                              
         MOVLW    40h 
         ADDWF    PHASE_CNT,F 
         RLCF     PHASE_CNT,W    ; Shift bit7 -> CARRY -> PORTB
; 
         BTFSC    SELECT_PIN 
         RLCF     LATB,F         ; was PORTB
         BTFSS    SELECT_PIN 
         RRCF     LATB,F         ; was PORTB
;-------------------------------------------------- 
UPD_TIMER: 
         BTFSS    UPD_TMR_VAL 
         BRA      UPD_TIMER_END 
         BCF      UPD_TMR_VAL 
; 
         MOVFF    TMR_VAL,CCPR1L 
         MOVFF    TMR_VAL+1,CCPR1H 
UPD_TIMER_END: 
;-------------------------------------------------- 
INT_SERVE_END: 
         BCF      PIR1,CCP1IF 
; 
         RETFIE   FAST          ; Use fast return stack          
;=======================================

Can we make routine shorter by updating timer values every interrupt even no change in value ?
These 3 instructions will be gone:
BTFSS UPD_TMR_VAL
BRA UPD_TIMER_END
BCF UPD_TMR_VAL

The UPD_TMR_VAL serves as a semaphore to lockout updates while TMR_VAL is being changed in the main program. An interrupt can occur between writes to TMR_VAL and TMR_VAL+1. For example, while changing from 00FFh to 0100h, the value written to CCPR1 would be 01FFh.

Will interrupt automatically save stack or I need to execute
CALL <adr>, fast ?

No need. Interrupts automatically save the context to the fast register stack.

Any additional configuration registers I need to set?

None.

really like the idea to use RLCF and RRCF instead of table !!!

The code can also be used on the PIC16F to try while you're waiting for the PIC18F242. RLCF is the same as RLF and RRCF is the same as RRF.
 
Motion:

Can you explain this code please:
MOVLW 40h
ADDWF PHASE_CNT,F
RLCF PHASE_CNT,W ; Shift bit7 -> CARRY -> PORTB


Why we can't use:
RLNCF LATB,1

ie why use "carry" if it can be rolled no carry?

Thank you
 
You are right if LATB were initialized to B'00110011' then it with cycle to the ff. with every RLNCF instruction to the ff.

00110011
01100110
11001100
10011001

The code will be reduced to simply the ff, saving 3 instructions:

Code:
INT_SERVE:                              
         BTFSC    SELECT_PIN 
         RLNCF    LATB,F         ; was PORTB 
         BTFSS    SELECT_PIN 
         RRNCF    LATB,F         ; was PORTB

However, this is not possible with the PIC16FXXX series because there is no equivalent instruction over there. To do it with that type of PIC, bit7 of PHASE_CNT is shifted out to the carry bit and subsequently shifted into PORTB. Besides, the PIC16FXXX does not have the LATB register and so a momentary short in the PORTB output could upset the above sequence.

Code:
PHASE_CNT                        PORTB
00000000 -> bit7 of PHASE_CNT -> 00000000
01000000 -> bit7 of PHASE_CNT -> 00000000
10000000 -> bit7 of PHASE_CNT -> 00000001
11000000 -> bit7 of PHASE_CNT -> 00000011
00000000 -> bit7 of PHASE_CNT -> 00000110
01000000 -> bit7 of PHASE_CNT -> 00001100
10000000 -> bit7 of PHASE_CNT -> 00011001
11000000 -> bit7 of PHASE_CNT -> 00110011
00000000 -> bit7 of PHASE_CNT -> 01100110
01000000 -> bit7 of PHASE_CNT -> 11001100
My mind was still in the PIC16FXXX series when I thought of this piece of code. These enhancements has gotten me fully converted to the PIC18F series for new designs.
 
Status
Not open for further replies.

Latest threads

New Articles From Microcontroller Tips

Back
Top