PDA

View Full Version : Interrut Driven Oscillator



barkerben
- 31st December 2004, 16:07
Hi, This program is supposed to use TMR1 interrupts to oscillate at a user defined frequency. The idea is that it will be integrated with a serial program so that the frequency can be set via a PC. I think the following makes sense, from what I have read / been told, but if anyone has a chance t have a quick glance that would be brilliant. Either, have a good New Years!

Cheers,


Ben


'***************************************
'* Name : OSCILLATOR_1
'* Author : Ben Barker
'* Notice : Copyright (c) 2004 Ben Barker
'* : All Rights Reserved
'* Date : 31/12/2004
'* Version : 1.0
'* PIC in use : 16F876
'***************************************


'Control Lines:
'---------------

ClockOut var portB.0
TRISB = TRISB = %00000000 'Set portB to outputs

'Software Defines:
'-----------------

TimerReload VAR WORD 'Reload value for TMR1
dummy VAR WORD 'dummy variable used to deal with 32 bit operations
DesiredFreq VAR WORD 'Desired frequency in mHz
postscaler VAR BYTE 'Postscaler software counter - TMR1 has no hardware postscaler


'Initialization routine:
'------------------------

init:
ADCON1 = 7 'Disable ADC
CMCON = 7 'Set PORTA to digital for future use


postscaler = 0 'This variable is incremented at each timer interrupt, then reset when it reaches 2 to give a 1:2 postscaler

''''''''''''''''''''''''''''''''''''''''''''
'This section sets the output frequency, and works as follows:
'
'We have set the postscaler to 1:2 and the postscaler to 1:8
'This, our TIMER1 frequency with a 4Mhz XT is 4000000/4/8/2 = 62500Hz
'This means it steps through 62500 steps per second.
'If we preload the timer so that there are only, for example, 6250 steps
'before overflowing again, then it will overflow every 0.1 sec - giving a 10Hz
'output. We define a reload value as the nmber of steps away from
'overflow we reset the timer to after each interrupt is triggered.
'
'62500/reload_value = freq_in_Hz
'
'Thus (62500*100)/reload_value = freq_in_mHz
'
'Now, TMR1 is a 16bit counter, so has 2^16 = 65536 steps available. TMR1 counts
'up from zero, so to get our actual reload value we need to calculate:
'65536-reload_value
'
'However, due to variable rollover, 65536-reload_value is equal to 0-reload_value
'(The sign is neglected, magnitude is the same)
'''''''''''''''''''''''''''''''''''''''''''''

DesiredFreq = 1000 'Set desired output frequency to 10Hz (specified in mHz)

Dummy = 62500
Dummy = Dummy * 1000
TMR1Reload = DIV32 DesiredFreq
TMR1Reload = 0 - TMR1Reload

ON INTERRUPT GOTO timer

PIE1.0=1 'Enable TMR1 interrupts
INTCON.6=1 'Enables Peripheral Interrupts
INTCON.7=1 'Enable Global interrupts
T1CKPS1 = 1 'Set up TMR1 prescaler to 1:8 (see data sheet)
T1CKPS0 = 1


'Main function:
'---------------

main:
'Loop forever here waiting for interrupts to occur
goto main

'Interrupt Service Routine (ISR):
'---------------------------------

timer:
DISABLE 'Disable interrupts during ISR (shouldn't be needed - if it is, program is taking too long anyway)

postscaler=postscaler+1 'increment postscaler
if postscaler=2 then 'check new postscaler value, and respond
postscaler=0
TOGGLE CLOCKOUT 'toggle clock output pin
endif

'This output is not entirely accurate, as the ISR takes finite time. Rather than TMR1
'overflowing and then immediately sarting from preloaded value, it overflows, starts
'counting, and is then reloaded. This introduces a small error - could be ironed out
'by quantifying latency and adjusting reload value accordingly.

TMR1L = TMR1Reload.lowbyte 'Reload timer with value to give needed frequency
TMR1H = TMR1Reload.highbyte
PIR1.0= 0 'Reset interrupt flag

ENABLE 'Re-enable interrupts

RESUME 'Return to main program loop

Darrel Taylor
- 1st January 2005, 22:25
Hi Ben,

To over come the Timer Re-load problem. You can ADD the TMR1Reload value to the current TMR1 count, instead of just loading the value and losing the time it took to get there.

This way the frequency will stay the same. Only the rising and falling edges will vary depending on how long it takes to service the interrupt.

The best way to ADD the value in is with a little ASM because it's easier to figure out how long it will take.
ASM
RST?RP ; Set to BANK0
BCF T1CON,TMR1ON ; Turn off timer
MOVF _TMR1Reload ,W ; 1
ADDWF TMR1L,F ; 1 ; reload timer with correct value
BTFSC STATUS,C ; 1/2
INCF TMR1H,F ; 1
MOVF _TMR1Reload+1,W ; 1
ADDWF TMR1H,F ; 1
BSF T1CON,TMR1ON ; 1 ; Turn TIMER1 back on
ENDASMThis way, you know for sure that it takes 7 cycles to reload the timer. Just add 7 to the TMR1Reload that you already have.
TMR1Reload = 0 - TMR1Reload + 7You'll also need to place TMR1Reload in BANK0
TimerReload VAR WORD BANK0 'Reload value for TMR1
HTH,
   Darrel

barkerben
- 1st January 2005, 23:19
Hi - Thanks for that - that was similar to what I thought might be needed. In fact, accurate frequencies are not that important, as long as the rates are consistent. However, since it shouldn't add much to the length of the code it is certainly worth including. As far as I can see the basic structure of the code is ok... The idea is I will combine this with serial driven interrupts so that a PC can control the output frequency etc. Servicing the interrupt routine in time may be tricky, but with 300 baud I hope to be ok!

I had a couple of questions about the ASM though - sorry If I've missed something obvious - I have used assembler before, but am a little rusty...:

RST?RP ; Set to BANK0
Firstly, what is the significance of the '?' - is it a standard ASM

MOVF _TMR1Reload ,W ; 1

Also, what is the significance of the _ prefix to the variable names as above in ASM...?

Finally, what is the significance of using Bank 0

I'll be back in the lab next week, so will let you know how it goes. Thanks for your help, and for the other people who have helped me out on the forum.


Cheers,


Ben

barkerben
- 2nd January 2005, 00:03
Ah - sorry about that. I see the underscore is needed is asm to refer to basic variables, and that BANK0 needs to be specified because that is the one chosen in the ASM code. Presumably its choice was arbitrary, but once chosen, consistency between the variable declaration and the ASM code was needed.

However, I am still unsure about these 3 lines of code:


BTFSC STATUS,C
INCF TMR1H,F
MOVF _TMR1Reload+1,W

Why can we not merely have:

RST?RP
BCF T1CON,TMR1ON
MOVF _TMR1Reload ,W
ADDWF TMR1L,F
ADDWF TMR1H,F
BSF T1CON,TMR1ON


I have a feeling it must be something to do with the problems writing to a 16 bit timer, but can't quite get it clear. I will probably be very embarassed when I realsie how simple it is, but till then a hint would be brilliant.


Cheers,


Ben

Darrel Taylor
- 2nd January 2005, 00:09
RST?RP is a Macro that comes with PBP. It's not a standard ASM directive.

It's pretty much the same thing as this
bcf STATUS, RP1
bcf STATUS, RP0
PREV_BANK = 0Except it optimizes out anything that is not needed. So if BANK1 is currently selected (RP1 = 0, RP0 = 1) then all it will compile to is
bcf STATUS, RP0
PREV_BANK = 0Saving 1 word of code space and 1 instruction cycle.

If the current bank was already BANK0, then no code is generated, saving 2 words and 2 instruction cycles. When you are changing banks several hundred times in a program, the savings can be considerable.

For more info on the "?" character, check out one of my other posts here.
http://www.picbasic.co.uk/forum/showthread.php?s=&threadid=539#post2009

The underscore prefixing the variable name is require for any variables that are defined in PBP. PBP adds the underscore to make sure that variable names don't conflict with other symbol names used by either PBP or the ASM compiler (PM or MPASM)

Placing the variable in BANK0 let's the ASM routine access it without having to figure out what bank it's in first. It would add more cycles to the routine, depending on which bank it was located in. That would change the number of cycles it takes to re-load the timer. Instead of 7, it could be 9 or 11 throwing off the timing.

For the RS232 part, you shouldn't have any problems. RS232 is very slow. You should be able to run at a much higher baud rate without missing anything. Just be sure to use the Hardware USART with interrupts. SERIN2 timing is software based, and as such will mean that other "ON INTERRUPTS" will not occur untill the SERIN/OUT has completed, many milliseconds later.

Best regards,
    Darrel

Darrel Taylor
- 2nd January 2005, 00:22
Ben,

Maybe a few more comments in the ASM routine will help.
ASM
RST?RP ; Set to BANK0
BCF T1CON,TMR1ON ; Turn off timer
MOVF _TMR1Reload ,W ; 1 Get LOW byte of TMR1Reload
ADDWF TMR1L,F ; 1 add it to LOW byte of TMR1
BTFSC STATUS,C ; 1/2 If addition caused a carry
INCF TMR1H,F ; 1 add carry to HIGH byte of TMR1
MOVF _TMR1Reload+1,W ; 1 Get HIGH byte of TMR1Reload
ADDWF TMR1H,F ; 1 add it to HIGH byte of TMR1
BSF T1CON,TMR1ON ; 1 Turn TIMER1 back on
ENDASMHTH,
    Darrel