• PicŪ Basic


  • Re: Creates Interrupt driven PWM signals from any 2 pins

    Here's a small program that creates interrupt driven PWM signals from any 2 pins (pin amounts can be increased or decreased). The PWM signal operates in the background,.

    It operates up to 16-bit and produces very low frequency PWM. The program below is for 10-bit operation and creates approx 30Hz.

    Frequencies can easily be altered by altering the Timer0 interrupt rate, or moving the resolution to 8-bits, instead of 16. i.e. Use Byte variables instead of Word types etc...

    Code:
    '
    ' Create a software (up to 16-bit) PWM interrupt
    '
        Device = 18F25K22
        Declare Xtal = 64
    
        On_Hardware_Interrupt GoTo ISR_Timer0               ' Point the hardware interrupt to its handler subroutine
    '
    ' Variables used within the interrupt handler
    ' Made system types so they stay in bankless Access RAM
    '
        Dim PWM_wIntCount As Word System                    ' Holds the counter for the duty cycles
        Dim PWM_wDutyCycle1 As Word System                  ' Holds duty cycle for PWM channel 0
        Dim PWM_wDutyCycle2 As Word System                  ' Holds duty cycle for PWM channel 1    
        Dim PWM_bFlags As Byte System                       ' Used for software flags
        Dim PWM_tBegin As PWM_bFlags.0                      ' True if it is OK to alter the duty values within the interrupt
    '
    ' PWM Pin definitions
    '
    $define PWM_Pin1 PORTA.0                                ' PWM 1 pin
    $define PWM_Pin2 PORTA.1                                ' PWM 2 pin
    
        Dim wTimer0 As TMR0L.Word                           ' Create a word sized Timer0 for reading and writing
    
    $define TIMER_INT_OFF  $7F                              ' Timer0 Interrupt disabled
    $define TIMER_INT_ON   $FF                              ' Timer0 Interrupt enabled
    '
    ' Timer 0 Declarations
    ' Timer0 configuration masks -- to be 'anded' together
    '
    $define T0_16BIT       $BF                              ' Timer0 is in 16 Bit mode
    $define T0_8BIT        $FF                              ' Timer0 is in 8 bit mode
    $define T0_SOURCE_INT  $DF                              ' Internal clock source
    $define T0_SOURCE_EXT  $FF                              ' External clock source
    $define T0_EDGE_RISE   $EF                              ' External rising edge clocks timer0
    $define T0_EDGE_FALL   $FF                              ' External falling edge clocks timer0
    $define T0_PS_1_1      $FF                              ' Prescaler 1:1 (No Prescaler)
    $define T0_PS_1_2      $F0                              '           1:2
    $define T0_PS_1_4      $F1                              '           1:4
    $define T0_PS_1_8      $F2                              '           1:8
    $define T0_PS_1_16     $F3                              '           1:16
    $define T0_PS_1_32     $F4                              '           1:32
    $define T0_PS_1_64     $F5                              '           1:64
    $define T0_PS_1_128    $F6                              '           1:128
    $define T0_PS_1_256    $F7                              '           1:256
        
    $ifndef True
        $define True 1
    $endif
    $ifndef False
        $define False 0
    $endif
    '
    ' Create variables for the demo main program
    '
        Dim PWM_wValue1 As Word
        Dim PWM_wValue2 As Word
    
    '--------------------------------------------------------------------------------------------
        GoTo Main                                       ' Jump over the subroutines
    '--------------------------------------------------------------------------------------------
    ' Interrupt Handler for two software PWM signals using a simple Timer0 overflow interrupt.
    ' The resolution is up to 16-bits per channel
    ' Input     : PWM_wDutyCycle1, PWM_wDutyCycle2 hold the Duty Cycles for channels 1 and 2
    '           : PWM_tBegin is true when the interrupt is able to have its duty cycle variables altered
    '
    ISR_Timer0:
        Context Save
        
        If INTCONbits_T0IF = True Then                      ' Is it a Timer0 interrupt?
            Dec PWM_wIntCount                               ' Yes. So decrement the PWM_wIntCount variable
            If PWM_wIntCount = 0 Then                       ' If PWM_wIntCount is 0, then it is time to start a new PWM signal period
                Clear PWM_Pin1                              ' \
                Clear PWM_Pin2                              ' / Pull both PWM output pins low
                PWM_wIntCount = 1023                        ' Set PWM_wIntCount (this is for 10-bits operation)
                PWM_tBegin = True                           ' Set the flag for the main software loop
            Else                                            ' Otherwise.. if PWM_wIntCount is not zero then check the duty cycle of the signal
                '
                ' If it's not the beginning of the PWM period, we need to compare each duty cycle to the value of PWM_wIntCount.
                ' When a match occurs, the output pin for the PWM channel is set to 1.
                '
                If PWM_wIntCount = PWM_wDutyCycle1 Then     ' Is PWM_wIntCount the same value as PWM_wDutyCycle1?
                    Set PWM_Pin1                            ' Yes so set output pin to 1
                EndIf
                If PWM_wIntCount = PWM_wDutyCycle2 Then     ' Is PWM_wIntCount the same value as PWM_wDutyCycle2?
                    Set PWM_Pin2                             ' Yes so set output pin to 1
                EndIf
            EndIf
            INTCONbits_T0IF = 0                             ' Clear the Timer0 overflow flag
        EndIf
    
        Context Restore                                     ' Exit the interrupt
    
    '---------------------------------------------------------------------------------
    ' Setup Timer0
    ' Input     : Bit definitions to configure Timer0
    ' Output    : None
    ' Notes     : The bit definitions for pConfig can be found at the top of this file, as $defines.
    '
    $define Timer0_Open(pConfig) _Timer0_Open pConfig
    
    _Timer0_Open Macro pConfig
        #if((Prm_1 != Num8) && (Prm_1 != Num16) && (Prm_1 != Num32))
            #error "OpenTimer0 Macro parameter must be a constant value"
            Exitm
        #endif
    
        #if((127 & pConfig) == 0)
            Clear  T0CON 
        #else
            Num_SFR (127 & pConfig),T0CON   ' Configure Timer0, but don't start it yet
        #endif
        Clear wTimer0                       ' Reset Timer0
        Clear INTCONbits_T0IF               ' Clear Timer0 overflow flag
        #if(pConfig & 128)                  ' If interrupts enabled
            Set INTCONbits_T0IE             ' Enable Timer0 overflow interrupt
        #else
            Clear INTCONbits_T0IE           ' Disable Timer0 overflow interrupt
        #endif
        Set T0CONbits_TMR0ON
    Endm
        
    '--------------------------------------------------------------------------------------------
    ' Initialisation for software PWM interrupt
    ' Input     : None
    ' Output    : None
    ' Notes     : None
    '
    Sub PWM_Init()
    '
    ' Configure Timer0 for:
    '                       Interrupt on Timer0 overflow
    '                       8-bit operation
    '                       Internal clock source
    '                       1:2 Prescaler
    '
        Timer0_Open(TIMER_INT_ON & T0_8BIT & T0_SOURCE_INT & T0_PS_1_2)
    '
    ' Variable and port defaults
    '
        Low PWM_Pin1                                ' \
        Low PWM_Pin2                                ' / Make the PWM pins outputs
        PWM_tBegin = True                           ' Make the interrupt change on the first occurance
        Clear PWM_wIntCount                         ' Reset PWM_wIntCount
        Clear PWM_wDutyCycle1                       ' \
        Clear PWM_wDutyCycle2                       ' / Clear the Duty Cycle Variables
        Set INTCONbits_GIE                          ' Enable global interrupts
    EndSub
    
    '--------------------------------------------------------------------------------------------
    ' Alter the duty cycle for both PWM channels
    ' Input     : pDuty1 holds the 10-bit duty cycle for PWM channel 1
    '           : pDuty2 holds the 10-bit duty cycle For PWM channel 2
    ' Output    : None
    ' Notes     : Uses the flag PWM_tBegin to see if it is OK to alter the duty values in the interrupt
    '           : Made inline for efficiency
    '
    $define PWM_DutyCycles(pDuty1, pDuty2) '   
        If PWM_tBegin = True Then          '
            PWM_tBegin = False             '
            PWM_wDutyCycle1 = pDuty1       '
            PWM_wDutyCycle2 = pDuty2       '
        EndIf
            
    '--------------------------------------------------------------------------------------------
    ' The main program loop starts here
    ' As a demo, increase the duty cycle for both PWM channels
    '
    Main:
        PWM_Init()                                      ' Initialise the Software PWM interrupt
    
        PWM_wValue1 = 0
        PWM_wValue2 = 0  
        Do
            PWM_DutyCycles(PWM_wValue1, PWM_wValue2)    ' Alter the duty cycles of the PWM channels
            DelayMS 100           
            Inc PWM_wValue1
            Inc PWM_wValue2                      
        Loop
        
        
    '--------------------------------------------------------------------------------------------
    ' Setup for 64MHz operation from a 16MHz crystal
    '
    Config_Start
        FOSC = HSHP           ' HS oscillator (high power > 16 MHz)
        PLLCFG = On           ' Oscillator multiplied by 4
        PRICLKEN = On         ' Primary clock enabled
        FCMEN = Off           ' Fail-Safe Clock Monitor disabled
        IESO = Off            ' Internal/External Oscillator Switchover mode disabled
        PWRTEN = On           ' Power up timer enabled
        BOREN = SBORDIS       ' Brown-out Reset enabled in hardware only (SBOREN is disabled)
        BORV = 190            ' Brown Out Reset Voltage set to 1.90 V nominal
        WDTEN = Off           ' Watch dog timer is always disabled. SWDTEN has no effect.
        WDTPS = 128           ' Watchdog Timer Postscale 1:128
        CCP2MX = PORTC1       ' CCP2 input/output is multiplexed with RC1
        PBADEN = Off          ' PORTB<5:0> pins are configured as digital I/O on Reset
        CCP3MX = PORTB5       ' P3A/CCP3 input/output is multiplexed with RB5
        HFOFST = On           ' HFINTOSC output and ready status are not delayed by the oscillator stable status
        T3CMX = PORTC0        ' Timer3 Clock Input (T3CKI) is on RC0
        P2BMX = PORTB5        ' ECCP2 B (P2B) is on RB5
        MCLRE = EXTMCLR       ' MCLR pin enabled, RE3 input pin disabled
        STVREN = Off          ' Stack full/underflow will not cause Reset
        LVP = Off             ' Single-Supply ICSP disabled
        XINST = Off           ' Instruction set extension and Indexed Addressing mode disabled (Legacy mode)
        Debug = Off           ' Disabled
        Cp0 = Off             ' Block 0 (000800-001FFFh) not code-protected
        CP1 = Off             ' Block 1 (002000-003FFFh) not code-protected
        CP2 = Off             ' Block 2 (004000-005FFFh) not code-protected
        CP3 = Off             ' Block 3 (006000-007FFFh) not code-protected
        CPB = Off             ' Boot block (000000-0007FFh) not code-protected
        CPD = Off             ' Data EEPROM not code-protected
        WRT0 = Off            ' Block 0 (000800-001FFFh) not write-protected
        WRT1 = Off            ' Block 1 (002000-003FFFh) not write-protected
        WRT2 = Off            ' Block 2 (004000-005FFFh) not write-protected
        WRT3 = Off            ' Block 3 (006000-007FFFh) not write-protected
        WRTC = Off            ' Configuration registers (300000-3000FFh) not write-protected
        WRTB = Off            ' Boot Block (000000-0007FFh) not write-protected
        WRTD = Off            ' Data EEPROM not write-protected
        EBTR0 = Off           ' Block 0 (000800-001FFFh) not protected from table reads executed in other blocks
        EBTR1 = Off           ' Block 1 (002000-003FFFh) not protected from table reads executed in other blocks
        EBTR2 = Off           ' Block 2 (004000-005FFFh) not protected from table reads executed in other blocks
        EBTR3 = Off           ' Block 3 (006000-007FFFh) not protected from table reads executed in other blocks
        EBTRB = Off           ' Boot Block (000000-0007FFh) not protected from table reads executed in other blocks
    Config_End
    With the program above, the main demo demo part can be removed, and the rest can be made an include file.

    Here is the above demo program, operating in the Isis simulator:
    This article was originally published in forum thread: HPWM low frquency limitation started by yvesmazzon View original post
  • Recent Activity

    See_Mos-247

    Mysterious PORTB problem

    Thread Starter: xldaedalus

    I'm using Proton+ to develop firmware for a product with switches. The MCU is an 18F26K22. Most of the switches reside on PORTB. I am NOT using a...

    See_Mos Today, 10:05 Go to last post
    towlerg-21522

    Pic16f18877 oread

    Thread Starter: evoortman

    Hi, On a PIC16F18877 the OREAD command doesn't seem to work. The code is working on a PIC16F1939. Both controllers use 32MHz int osc. If i...

    towlerg Today, 13:23 Go to last post