Hello again,
Although I still think a PIC with a CCP module would be "better" suited I gave the 12F675 a chance and came up with the following idea.
Code:
'****************************************************************
'* Name : FrequencyScaler.pbp *
'* Author : Henrik Olsson *
'* Notice : Copyright (c) 2011 Henrik Olsson *
'* : No Rights Reserved *
'* Date : 2011-06-21 *
'* Version : 1.0 *
'* Notes : Untested, provided as a starting point. *
'* : *
'****************************************************************
' We need the TMR0 prescaler, therefor it can't be used for the WDT.
' Make sure to turn WDT OFF in CONFIGs.
DEFINE NO_CLRWDT 1
DEFINE OSC 4 ' 4Mhx x-tal
' The lower value ScaleFactor is the higher the output frequency will
' become. (It scales down the period time). To multiply the frequency by
' 1.75 set ScaleFactor to 65536/1.75 = 37449 (Minimum allowed input frequency: 8.8Hz)
' 2.00 set ScaleFactor to 65536/2.00 = 32768 (Minimum allowed inout frequency: 7.6Hz)
' 2.25 set ScaleFactor to 65536/2.25 = 29127 (Minimum allowed input frequency: 6.8Hz)
' The minimum output frequency is 1 / 0.065536 = 15.258Hz meaning that
' the minimum input frequency is (1 / 0.065536) * (ScaleFactor / 65536).
' There is NO error checking, overflowing the calculation by inputting a
' lower frequency WILL make the output behave accordingly.
ScaleFactor CON 32768 ' Scalefactor
InputSignal VAR GPIO.0 ' The input signal is connected here.
AquireState VAR BYTE ' State machine variable, keep track of what we' re doing.
Waiting CON 0 ' States.
FirstHalf CON 1
SecondHalf CON 2
Input_Period VAR WORD ' Period of input signal, in timer ticks.
Output_Period VAR WORD ' Period of output signal, same "scale" as input.
TMR0_Reload VAR BYTE ' Reload value for TMR0 to get correct output frequency
FudgeFactor VAR BYTE ' For finetuning.
StepCount VAR BYTE ' Used to "step thru" the output pattern.
Running VAR BIT ' Gets set to 1 after getting our first sample
OldPinState VAR BIT ' Previous state of input signal.
CLEAR
' The lowest expected input frequency is 10Hz. At 4Mhz that's 100000 ticks which
' won't fit in the 16bit wide TMR1. We must use a prescaler of 1:2 - lowest
' possible frequency is then ~7.6Hz.
T1CON.5 = 0 ' TMR1 presecaler 1:2
T1CON.4 = 1
' We will work with the TMR0 prescaler later but for now make sure
' it's assigned to TMR0 with a ratio of 1:1.
OPTION_REG.5 = 0 ' TMR0 clocked from Fosc/4
OPTION_REG.3 = 0 ' TMR0 prescaler assigned to TMR0
OldPinState = InputSignal ' Initial state of input signal.
' ------------------------------------------------------------------------------
' ------------- Main routine implemented as a state machine --------------------
' ------------------------------------------------------------------------------
Main:
' First we wait for a transition on the input pin.
If (AquireState = WAITing) AND (OldPinState != InputSignal) THEN
OldPinState = InputSignal ' Remember the pin state.
GOSUB StartAquiring ' Start the clock
AquireState = FirstHalf ' Getting first half of period.
ENDIF
' Then we wait for a second transition on the input pin.
If (AquireState = FirstHalf) AND (OldPinState != InputSignal) THEN
OldPinState = InputSignal ' Remember the pin state
AquireState = SecondHalf ' Getting second half of period.
ENDIF
' Finally we wait for a third transition on the input pin. When
' that transition occurs we have "captured" both the high and low
' "halfcycle" of the input signal.
If (AquireState = SecondHalf) AND (OldPinState != InputSignal) THEN
OldPinState = InputSignal ' Remember the pin state
GOSUB StopAquiring ' Stop the clock and calculate
AquireState = Waiting ' Wait for next transition.
ENDIF
' At the same time we're looking at the TMR0 interrupt flag to
' determine if it's time to do something with the outputs.
IF (Running = 1) AND (INTCON.2 = 1) THEN
GOSUB DoDatThing
ENDIF
Goto Main
' -----------------------------------------------------------------------------
StartAquiring:
TMR1H = 0 ' Reset timer 1 count.
TMR1L = 0
T1CON.0 = 1 ' Start TMR1
RETURN
' -----------------------------------------------------------------------------
' -----------------------------------------------------------------------------
StopAquiring:
T1CON.0 = 0 ' Stop TMR1
Input_Period.HighByte = TMR1H
Input_Period.LowByte = TMR1L ' Get TMR1 count.
' Now apply the math to get our desired output period. Because of the TMR1 prescale
' ratio of 1:2 Input_Period now ranges from 50000 (=10Hz) to 250 (=2000Hz).
' We need to account for that 1:2 ratio and we do that by multiplying the scaled value by
' by 2 resulting in a period time ticks.
Output_Period = Input_Period ** ScaleFactor ' Scale the value
Output_Period = Output_Period << 1 ' Account for TMR1 prescaler
' If this is the very first period captured we enable
' the "output frequency generator".
If Running = 0 then
TMR0 = 0 ' Reset TMR
INTCON.2 = 0 ' Reset interrupt flag.
GOSUB DoDatThing
Running = 1
ENDIF
RETURN
' -----------------------------------------------------------------------------
' -----------------------------------------------------------------------------
DoDatThing:
INTCON.2 = 0 ' Reset interrupt flag.
' We have a problem in that Output_Period now contains a value ranging from
' 50000 to 250 for 20 to 4000Hz respectively. TMR0 is only 8bits wide so we must
' use its prescaler to get enough time out of it. The problem with that that to
' to able to "fit" 50000 ticks we must use a prescale ratio of 1:256 but then
' the resoultion at the higher frequencies becomes terrible. For this reason
' we select a suitable prescaler ratio based on the actual output period.
' This gives as good resolution at the top end at the same time as it allows
' us a wide range of output frequencies.
Select case Output_Period
Case IS < 256
' Set prescaler to 1:1 (prescaler assigned to WDT). Datasheet mentions
' special attention is needed when doing this - investigate further !
OPTION_REG = OPTION_REG & %11111000
'Output_Period = Output_Period >> 0 ' Don't divide anything
Case IS < 512
' Precalser 1:2
OPTION_REG = OPTION_REG & %11110000
Output_Period = (Output_Period +1 ) >> 1 ' Divide by 2
Case IS < 1024
' Prescaler 1:4
OPTION_REG = OPTION_REG & %11110001
Output_Period = (Output_Period + 2) >> 2 ' Divide by 4
Case IS < 2048
' Prescaler 1:8
OPTION_REG = OPTION_REG & %11110010
Output_Period = (Output_Period + 4) >> 3 ' Divide by 8
Case IS < 4096
' Presacler 1:16
OPTION_REG = OPTION_REG & %11110011
Output_Period = (Output_Period + 8) >> 4 ' Divide by 16
CASE IS < 8192
' Prescaler 1:32
OPTION_REG = OPTION_REG & %11110100
Output_Period = (Output_Period + 16) >> 5 ' Divide by 32
Case IS < 16384
' Prescaler 1:64
OPTION_REG = OPTION_REG & %11110101
Output_Period = (Output_Period + 32) >> 6 ' Divide by 64
Case IS < 32768
' Prescaler < 1:128
OPTION_REG = OPTION_REG & %11110110
Output_Period = (Output_Period + 64) >> 7 ' Divide by 128
Case else
' Prescaler < 1:256
OPTION_REG = OPTION_REG & %11110111
Output_Period = (Output_Period + 128) >> 8 ' Divide by 256
END SELECT
' Now we have a TMR0 prescaler suitable for the output period we want and
' we have scaled down the value in Output_Period to fit the 8bit wide TMR0.
' Let's say out Output_Period initially was 12345. That would make the
' above code set TMR0 prescaler to 1:64 and then do (12345 + 32) / 64 = 193.
' Adding 32 rounds of the otherwise truncated result to the nearest integer.
' At times there WILL be a slight error though. In this case the true output
' period is 12345us but we'll get 34*193=12352 instead.
' Because TMR0 interrupts on overflow we now need to subtract our period
' value from 256 and load that value into TMR0.
TMR0_Reload = (256 - Output_Period) + FudgeFactor
' Temporarily stop TMR0 by switching its clock source to GP2. Reload it
' with the calculated value and the restart it. By adding the reload value
' instead of overwriting TMR0 we account for the time passed before
' actually getting to this part of the code.
OPTION_REG.5 = 1 ' Stop
TMR0 = TMR0 + TMR0_Reload ' Reload
OPTION_REG.5 = 0 ' Start
' -----------------------------------------------------------------------------
' This is where the actual output switching gets done.
' Just example here, do whatever but remeber to set TRIS accordingly.
' Also look out for RMW issues.
' -----------------------------------------------------------------------------
Select Case StepCount
Case 0
GPIO.1 = 1
GPIO.2 = 0
GPIO.3 = 0
Case 1
GPIO.1 = 0
GPIO.2 = 1
GPIO.3 = 0
Case 2
GPIO.1 = 0
GPIO.2 = 0
GPIO.3 = 1
End SELECT
StepCount = StepCount + 1
If StepCount = 3 then StepCount = 0
RETURN
It compiles without errors (to just about half the space of the 12F675) but I currently have no way of actually testing it so that's up to anyone.
Because the input and TMR0 interrupt flag are monitored in software there will be some jitter both in the measured input period time AND in the generated output frequency. The change in output signal frequency will always "lag" the change in input signal frequency by 1.5 cycles, I don't know if that's OK or not.
I commented the heck out of the code in hopes it would make sense. In case it doesn't, just ask and I'll try to explain why I thought that would be a good idea.... Again, this is untested code but perhaps it can serve as a starting point.
/Henrik.
Bookmarks