Sinewaves using interrupts.


Closed Thread
Results 1 to 26 of 26

Hybrid View

  1. #1
    Join Date
    Mar 2009
    Posts
    653


    Did you find this post helpful? Yes | No

    Default

    Ok, after staring at your code for 2 hours (ie until I actually looked like my Avatar!) .....your code slowly morphs into a magic eye puzzle with the words "Hank is a bit slow" popping out in 3D.

    I'd like to get to the bottom of how you set the frequency up for DDS related stuff - I think I get the jist.

    You are preloading timer1 with a 'preset count' to change the sine wave frequency (in your example FD85 ,which in decimal is 64901)

    Therefore Timer 1 is a 32 bit timer, which means it'll overlow at 65535 ...so you have 65535 - 64901 = 351 counts, before the code jumps to the interrupt handler (towards reading the next value in the sine wave array).

    Have I got this right?

    Ok, my PIC won't run at 40Mhz, so I can't copy your example 'like for like', but I'm getting an odd result - don't get me wrong, I get a decent sine wave, but not at the frequency I was expecting.

    Basically, when I type my particular settings into Mister_E's calc & divide his Interrupt frequency result by 32 (this being the amount of interrupts it takes to make one complete sine wave cycle?) ...the result is different to what I'm seeing on myscope!

    So...my settings.

    20Mhz Oscillator (ceramic resonator)
    16 bit timer
    1:1 prescaler
    Timer1 preloaded with 65122

    According to Mr E's calculator that should give an interrupt frequency of 12.048Khz - it should be just a matter of dividing that by 32 to get the sine wave coming out of the filter? (ie it takes 32 interrupts to build a full sine wave)

    therefore 12048/32 = 376.5Hz.

    But I'm seeing a 348Hz sine wave on my scope??

    So I wanted to check the actual interrupt frequency, therefore I added a 'toggle a pin' into your interrupt handler - that resulting square wave output registers @5580Hz ...this needs to be doubled this to get the actual interrupt rate, therefore the interrupt rate appears to be just 11160Hz (which if I divide by 32, surprise surprise is what my scope is seeing as a sine wave - 348.75Hz)

    I guess it takes time to service the interrupts & actually do stuff in the routine ...but that equates to about 30 timer1 counts going AWOL ...which seems a lot?

    So any idea why the discrepancy between Mister_E's calculator (which is surely right) & what I'm seeing on my scope (my scope is accurate btw!).



    Green trace (& text) relates to the sine wave, yellow trace relates to the square wave from the interrupt pin toggling (to trap the actual 'interrupt' frequency externally on my scope)

    Code:
    timerone var word 
     
    INCLUDE "DT_INTS-14.bas"     ; Base Interrupt System
    ;include "ReEnterPBP-18.bas"  ;not needed for ASM type interrupt service routines
     
    ASM
     
    INT_LIST  macro    ; IntSource,        Label,  Type, ResetFlag?
            INT_Handler   TMR1_INT,   _sine,   ASM,  yes
        endm
        INT_CREATE               ; Creates the interrupt processor
    ENDASM
    
    T1CON.5 = 0
    T1CON.4 = 0
    
     
    TMR1L = 255
    TMR1H = 254
    @   INT_ENABLE  TMR1_INT     ; Enable Timer 1 Interrupts  
    'timerone = $FD85   ;gives about 60 htz sine                ;gives about 60 htz sine 
    timerone = 65122  ;gives about 60 htz sine                ;gives about 60 htz sine 
    
    Main:
            pause 1
    GOTO Main
     
    '---[TMR1_INT - interrupt handler]------------------------------------------
     
    sine:
        TMR1L = timerone.byte0
        TMR1H = timerone.byte1     
        CCPR1L = sineval[STEPCOUNT]>>1 
        stepcount = stepcount -1
        if stepcount = 0 then stepcount = 32
        toggle PortC.4 
     
    @    INT_RETURN

    Could this be just down to me using a ceramic resonator @20Mhz (I have no idea what spec these things have, but the discrepancy amount to some 10%?)
    Last edited by HankMcSpank; - 1st October 2010 at 00:36.

  2. #2
    Join Date
    Oct 2005
    Location
    Sweden
    Posts
    3,612


    Did you find this post helpful? Yes | No

    Default

    Hi Hank,
    I think you need to account for the time it takes to
    A) Get to the interrupt handler and
    B) Reload the timer.

    DT-Ints are GREAT but it needs to save a lot of variables before actually executing your interrupt code and that takes time. Once you get to actually reload the timer a certain amount of time has passed. If you don't account for that in your reload value you are basically "turning back the time" so to speak.

    Either just tweak the reload value untill you get the desired frequency or try adding the reload value to the current value of TMR1 which should account for any time passed between actual overflow and reload. I think you need to stop the timer, grab its value into a word variable, add your reload value and write it back you TMR1 registers then restart the timer. That means you likely need to tweak the reload value slightly anyway.

    HTH
    /Henrik.

  3. #3
    Join Date
    Mar 2009
    Posts
    653


    Did you find this post helpful? Yes | No

    Default

    Hi Henrik,

    That all makes sense....I guess I could slowly add stuff into the interrupt handler & check the scope interrupt frequency (vs Mr E's calculator), I should then be able to account for the overhead of adding each bit in ...and then build the compensation back into the code.

    I'd like to be able to generate the sine wave with a little more accuracy becuase an error of 10% is a little too much.

  4. #4
    Join Date
    Aug 2010
    Location
    Maryland, USA
    Posts
    869


    Did you find this post helpful? Yes | No

    Default

    Hank, You could "time" the time it takes to enter the interupt and return. First replace the pause with a check for flag. set the flag low in the startup, then in the handler:
    • copy tmrl and tmrh to a variable (int_enter)
      set your flag
      return
    your main routine will see the flag set so then:
    • stop timer
      jump out of main routine
      grab tmrl and tmrh to a new variable (int_exit)
      display int_enter and int_exit
      reload timer
      turn timer on
      reset flag
      return to main

    This way you will have real time values for the lag in and lag out. This will be very intresting numbers so please post them. Seems like anyone dealing with stuff in time critical situations will find value in this.

    Then you can add stuff to the handler to see how much time it takes to do x,y,z. In fact, I for one am very intrested in how long the "toggle" command takes. So taking that out and running to get some base numbers, then adding it back in, we will see the differance in the int_exit value.
    Heck, Some day I may even have to do this myself. I am courious how long every command in PBP takes. I am from the ASM club. As such, I am used to being able to just count instructions to get execution times. With PBP, I have no way of knowing. Heck, even back when I was messing with BS1, they stated each instruction "cost" 200uS! thats a ton of time, but you knew what it was so you can account for it.
    Last edited by cncmachineguy; - 1st October 2010 at 11:35. Reason: added
    -Bert

    The glass is not half full or half empty, Its twice as big as needed for the job!

    http://foamcasualty.com/ - Warbird R/C scratch building with foam!

  5. #5
    Join Date
    Mar 2009
    Posts
    653


    Did you find this post helpful? Yes | No

    Default

    Hi CNCMG (can I call you Bert?!))

    I get where you're coming from...I will dabble tonight.

    Fortunately (unlike my phase thread), such "Hmm, I wonder how many cycles it takes to do interrupt x" one that most on here can easily/readily do & experiment with too (no need for all pass filters etc).

    But yes, with such time sensitive stuff as generating a desired sine wave frequency, we all need to know the overhead involved in the generation code ....just so we can cater for them.

    For example, I was factoring in an a count of '30' to the Timer1 preload value to finally the actual frequency being targeted (vs what I was seeing), which suggests it takes 30 instruction cycles to handle all the interrupt to-ing & fro-ing?
    Last edited by HankMcSpank; - 1st October 2010 at 12:21.

  6. #6
    Join Date
    Aug 2010
    Location
    Maryland, USA
    Posts
    869


    Did you find this post helpful? Yes | No

    Default

    Quote Originally Posted by HankMcSpank View Post
    Hi CNCMG (can I call you Bert?!))
    Please do

    Fortunately (unlike my phase thread), such "Hmm, I wonder how many cycles it takes to do interrupt x" one that most on here can easily/readily do & experiment with too (no need for all pass filters etc).
    HAHA all but me, to date, I have no way of displaying the counts. All my projects so far have been control type stuff with no user interface. Couple that with being exclusive ASM (translate to BIG PAIN to display stuff), and you have me with no way to see inside. Don't get me wrong, ICD is great for looking at the registers and such, but I haven't looked into using PBP with MPLAB and ICD!

    My project list between work, play, and home life is quite full at the moment. So I am trying to jump start my PBP learning curve in cyberspace only.
    -Bert

    The glass is not half full or half empty, Its twice as big as needed for the job!

    http://foamcasualty.com/ - Warbird R/C scratch building with foam!

  7. #7
    Join Date
    Oct 2005
    Location
    Sweden
    Posts
    3,612


    Did you find this post helpful? Yes | No

    Default

    Hi,
    On a project I was working on I found that the latency with DT-INTS-18 was 29uS at 20Mhz, that's 145 cycles. I measured this with the scope triggering on the external signal that was firing the interrupt and then setting an output high as the very first thing in the ISR.

    29uS is is basically 30% of the time when interrupting at 12kHz then it takes roughly the same amount of time to restore all the registers when leaving the ISR.

    Now, this was with the ISR declared as a PBP handler. I now see that you have the handler declared as an ASM-handler (but still written in) PBP. This makes it quite a bit faster since DT-INTS doesn't save the PBP system variables. BUT because of this you need to be VERY (I mean VERY) careful about what you do in your ISR if you declare it as type ASM but writes it in PBP. In THIS case it may not matter since you aren't doing anything in your main routine but once you start to add stuff there AND have an interrupt handler using the system vars without saving them you are in trouble.

    I found that an interrupt declared as ASM with the DT-INTS has a latency of around 40 cycles.

    You can read about my findings in this thread.

    /Henrik.

  8. #8
    Join Date
    Mar 2009
    Posts
    653


    Did you find this post helpful? Yes | No

    Default

    Hi Henrik,

    thanks for the info (& the link to your thread - I'll read it tonight as it's not exactly a quick 'snappy read'!) - just to clarify, I'm using scalerobotic's code ....so anything declared as ASM, PBP etc...at this stage of my 'pic programming for dummies' learning curve.....blissfully washes over me! (in my naive programming world, I'm still convinced the earth is still flat & if you sail towards the horizon, you will fall off over the end)

    I've already picked up that when using ASM interrupts, that you have to be careful what you get up to in the interrupt handler (ie get in, get out asap - handle the stuff in your main code), but I've no intention of doing much in there (bar the odd port toggle to assist with scope observations!)

    Re generating sine waves - I've actually a couple of AD9835 ICs winging their way over the North Atlantic to me as I type - they seem to be a rather quite serious way of generating sine waves! (but while waiting I thought I'd have a dabble with this PWM method, just to start getting up to speed in the black art of Sinewaveology!)
    Last edited by HankMcSpank; - 1st October 2010 at 13:43.

  9. #9
    Join Date
    Aug 2005
    Location
    Michigan, USA
    Posts
    224


    Did you find this post helpful? Yes | No

    Default

    Hi Hank (and gang),

    I know I'm the assembly language "outsider" here but I agree that interrupt 'overhead' may be a problem. If you were writing the application in assembler I would make a couple suggestions like these;

    <1>
    Consider upgrading to one of the 'enhanced' 16F1x devices which can run at 32-MHz (125-nsec Tcy) with full interrupt context save and restore built-in (= very low interrupt overhead).

    <2>
    Research DDS (Direct Digital Synthesis) and consider using a DDS-PWM or a DDS-R2R method for generating your sine waves. It may not be a good fit for your particular application but it is relatively easy to characterize frequency parameters and performance. For example, here's the characterization for an ISR "engine" for a 10-KHz 12F1822 Sine/Square/Sawtooth/Triangle wave DDS-PWM Signal Generator (using a 32-MHz clock);

    Code:
    ;
    ;  Fdds (DDS frequency) is the reciprocal of the 8-usec PWM period...
    ;
    ;    Fdds (Freq DDS) = 1 / 0.000008 = 125000-Hz
    ;
    ;  frequency resolution is Fdds (DDS frequency) divided by
    ;  the phase accumulator bit width (2^24)...
    ;
    ;    Fres (Freq Resolution) = Fdds / 2^24 = 0.007450580596923828125 Hz
    ;
    ;  to calculate the phase offset for a 10-KHz Fout signal...
    ;
    ;    Phase = INT((Fout / Fres) + 0.5)
    ;    Phase = INT((10000 / 0.007450580596923828125) + 0.5) = 1342177
    ;
    ;    --< or >--
    ;    Phase = INT(Fout / (Fdds / 2^24) + 0.5) = 1342177
    ;    Phase = INT(2^24 / (Fdds / Fout) + 0.5) = 1342177
    ;
    ;  to calculate the actual frequency output (Fout)...
    ;
    ;    Fout = Phase * Fres
    ;    Fout = 1342177 * .007450580596923828125 = ~9999.998 Hz
    ;
    ;    --< or >--
    ;    Fout = Fdds / (2^24 / Phase) = ~9999.998 Hz
    ;    Fout = Phase / (2^24 / Fdds) = ~9999.998 Hz
    ;
            org     0x004
    v_int
            banksel PIR1            ; bank 0                          |B0
            bcf     PIR1,TMR2IF     ; clear TMR2 interrupt flag       |B0
            movf    Phase+0,W       ; Accum = Accum + Phase           |B0
            addwf   Accum+0,F       ;                                 |B0
            movf    Phase+1,W       ;                                 |B0
            addwfc  Accum+1,F       ;                                 |B0
            movf    Phase+2,W       ;                                 |B0
            addwfc  Accum+2,F       ;                                 |B0
            movf    Accum+2,W       ; use 8 most significant bits     |B0
            addwf   FSR1L           ; as pwm table (array) address    |B0
            movf    INDF1,W         ; WREG = duty cycle, 0..255       |B0
            banksel CCP1CON         ; bank 5                          |B5
            bcf     CCP1CON,DC1B0   ; clr duty cycle bit 0            |B5
            bcf     CCP1CON,DC1B1   ; clr duty cycle bit 1            |B5
            lsrf    WREG,W          ; shift duty cycle bit 0 into C   |B5
            skpnc                   ; bit 0 = 1? no, skip, else       |B5
            bsf     CCP1CON,DC1B0   ; set duty cycle bit 0            |B5
            lsrf    WREG,W          ; shift duty cycle bit 1 into C   |B5
            skpnc                   ; bit 1 = 1? no, skip, else       |B5
            bsf     CCP1CON,DC1B1   ; set duty cycle bit 1            |B5
            movwf   CCPR1L          ; set duty cycle bits 7..2        |B5
            retfie                  ;                                 |B?
    ;
    The ISR "engine" for the little 12F1822 USB Signal Generator uses 26 cycles (3.25-usecs) or about 40% of the 64 cycles between interrupts. That leaves plenty of time in the main program to poll the serial port and collect a new signal "type" character and/or a new 24-bit frequency phase offset data from the PC host application.

    One problem I see trying to use DDS to generate sine waves for your application is the number of samples you might need to generate a phase shift. If you need 5° shifts it would require at least 72 samples per cycle. That means your upper frequency limit with a 125-KHz DDS would be only 1736 Hz. If you needed 2° resolution with a 5000 Hz upper frequency limit then you would need an Fdds of 900-KHz. That might be difficult even for the much faster DDS-R2R method.

    Cheerful regards, Mike
    Last edited by Mike, K8LH; - 1st October 2010 at 15:27.

Members who have read this thread : 0

You do not have permission to view the list of names.

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts