News:

;) This forum is the property of Proton software developers

Main Menu

Mirror Bit Angle Modulation (MIBAM) instead of PWM

Started by top204, Sep 08, 2023, 02:48 PM

Previous topic - Next topic

top204

Recently on the forum, one of our Positron8 users created a program that generated Mirror Bit Angle Modulation (MIBAM), in order to alter voltages on 8 pins using a digital method that differs from software PWM in that it takes less resources from the microcontroller.

Pepe's code triggered an interest in its method so I adapted his code to make it more procedural and easier to change for different devices, and the program is listed below:

'
'   /\\\\\\\\\
'  /\\\///////\\\
'  \/\\\     \/\\\                                                 /\\\          /\\\
'   \/\\\\\\\\\\\/        /\\\\\     /\\\\\\\\\\     /\\\\\\\\   /\\\\\\\\\\\  /\\\\\\\\\\\  /\\\\\\\\\
'    \/\\\//////\\\      /\\\///\\\  \/\\\//////    /\\\/////\\\ \////\\\////  \////\\\////  \////////\\\
'     \/\\\    \//\\\    /\\\  \//\\\ \/\\\\\\\\\\  /\\\\\\\\\\\     \/\\\         \/\\\        /\\\\\\\\\\
'      \/\\\     \//\\\  \//\\\  /\\\  \////////\\\ \//\\///////      \/\\\ /\\     \/\\\ /\\   /\\\/////\\\
'       \/\\\      \//\\\  \///\\\\\/    /\\\\\\\\\\  \//\\\\\\\\\\    \//\\\\\      \//\\\\\   \//\\\\\\\\/\\
'        \///        \///     \/////     \//////////    \//////////      \/////        \/////     \////////\//
'                                  Let's find out together what makes a PIC Tick!
'
' Mirror Bit Angle Modulation mechanism, created by Pepe on the forum
'
' Modified by Les Johnson for the Positron8 compiler.
'
    Device = 18F26K22                                   ' Tell the compiler what device to compile for
    Declare Xtal = 64                                   ' Tell the compiler what frequency the device will be operating at (in MHz)
    On_Hardware_Interrupt GoTo ISR_Handler              ' Point to the Interrupt Handler
'
' Setup USART1
'
    Declare HSerial1_Baud = 9600
    Declare HRsout1_Pin = PORTC.6
'
' Set the pins to use for the MIBAM outputs
'
$define MIBAM_Pin0 PORTB.0                              ' Set the pin to use for MIBAM channel 0
$define MIBAM_Pin1 PORTB.1                              ' Set the pin to use for MIBAM channel 1
$define MIBAM_Pin2 PORTB.2                              ' Set the pin to use for MIBAM channel 2
$define MIBAM_Pin3 PORTB.3                              ' Set the pin to use for MIBAM channel 3
$define MIBAM_Pin4 PORTB.4                              ' Set the pin to use for MIBAM channel 4
$define MIBAM_Pin5 PORTB.5                              ' Set the pin to use for MIBAM channel 5
$define MIBAM_Pin6 PORTB.6                              ' Set the pin to use for MIBAM channel 6
$define MIBAM_Pin7 PORTB.7                              ' Set the pin to use for MIBAM channel 7

    Dim MIBAM_wTimer0 As TMR0L.Word                     ' Combine TMR0L and TMR0H into a 16-bit SFR

'------------------------------------------------------------------------------
$define IntGlobal_Enable()  INTCONbits_GIE = 1          ' Enable global interrupts
$define IntGlobal_Disable() INTCONbits_GIE = 0          ' Disable global interrupts
$define IntPeriph_Enable()  INTCONbits_GIEL = 1         ' Enable peripheral interrupts
$define IntPeriph_Disable() INTCONbits_GIEL = 0         ' Disable peripheral interrupts

'------------------------------------------------------------------------------
' Timer0 Meta-Macros for a PIC18F26K22 device
'
$define Timer0_Flag()       INTCONbits_T0IF             ' Timer0 Flag
$define Timer0_FlagClear()  Timer0_Flag() = 0           ' Clear the Timer0 interrupt flag
$define Timer0_IntEnable()  INTCONbits_TMR0IE = 1       ' Enable a Timer0 interrupt
$define Timer0_IntDisable() INTCONbits_TMR0IE = 0       ' Disable a Timer0 interrupt
$define Timer0_Start()      T0CONbits_TMR0ON = 1        ' Start Timer0
$define Timer0_Stop()       T0CONbits_TMR0ON = 0        ' Stop Timer0
'
' Alter the bits for a required Timer0 Prescaler on a PIC18F26K22 device
'
$define Timer0_Prescaler(pPrescaler)'
    $if pPrescaler = 2              '
        T0CONbits_T0PS0 = 0         '
        T0CONbits_T0PS1 = 0         '
        T0CONbits_T0PS2 = 0         '
    $elseif pPrescaler = 4          '
        T0CONbits_T0PS0 = 1         '
        T0CONbits_T0PS1 = 0         '
        T0CONbits_T0PS2 = 0         '
    $elseif pPrescaler = 8          '
        T0CONbits_T0PS0 = 0         '
        T0CONbits_T0PS1 = 1         '
        T0CONbits_T0PS2 = 0         '
    $elseif pPrescaler = 16         '
        T0CONbits_T0PS0 = 1         '
        T0CONbits_T0PS1 = 1         '
        T0CONbits_T0PS2 = 0         '
    $elseif pPrescaler = 32         '
        T0CONbits_T0PS0 = 0         '
        T0CONbits_T0PS1 = 0         '
        T0CONbits_T0PS2 = 1         '
    $elseif pPrescaler = 64         '
        T0CONbits_T0PS0 = 1         '
        T0CONbits_T0PS1 = 0         '
        T0CONbits_T0PS2 = 1         '
    $elseif pPrescaler = 128        '
        T0CONbits_T0PS0 = 0         '
        T0CONbits_T0PS1 = 1         '
        T0CONbits_T0PS2 = 1         '
    $elseif pPrescaler = 256        '
        T0CONbits_T0PS0 = 1         '
        T0CONbits_T0PS1 = 1         '
        T0CONbits_T0PS2 = 1         '
    $else                           '
        $error "Unknown Timer0 prescaler value. Supported values for this device are 2 or 4 or 8 or 16 or 32 or 64 or 128 or 256" '
    $endif

'----------------------------------------------------------------------------------------
' Calculate the value to place into the TMR0L\H registers in order to achieve a certain overflow interrupt rate (in us)
'
$define Timer0_cPrescaler 2                                                     ' The prescaler used for the timer (must be the same prescaler as used for the timer's setup)
$define Timer0_cMicroSeconds 100                                                ' Interrupt rate (in uS)
$define Timer0_cTweakValue 0                                                    ' Holds a tweak value for the timer calculation

$define Timer0_cValue $eval ((65536 + Timer0_cTweakValue) - ((Timer0_cMicroSeconds / Timer0_cPrescaler) * (_xtal / 4)))

$if Timer0_cValue > 65535
    $error "Timer0_cValue is too large for the interrupt duration"
$elseif Timer0_cValue <= 0
    $error "Timer0_cValue is too small for the interrupt duration"
$endif

'----------------------------------------------------------------------------------------
$define PLL_Enable() OSCTUNEbits_PLLEN = 1                                      ' Device's oscillator PLL enable
$define PLL_ReadyFlag() OSCCON2bits_PLLRDY                                      ' Device's oscillator PLL stable status

$ifndef True
    $define True 1
$endif
$ifndef False
    $define False 0
$endif

'----------------------------------------------------------------------------------------
' Create variables and constants for the program
'
    Dim MIBAM_bDutyCycle0 As Byte                                               ' Holds the 8-bit duty cycle value for the MIBAM channel 0 pin
    Dim MIBAM_bDutyCycle1 As Byte                                               ' Holds the 8-bit duty cycle value for the MIBAM channel 1 pin
    Dim MIBAM_bDutyCycle2 As Byte                                               ' Holds the 8-bit duty cycle value for the MIBAM channel 2 pin
    Dim MIBAM_bDutyCycle3 As Byte                                               ' Holds the 8-bit duty cycle value for the MIBAM channel 3 pin
    Dim MIBAM_bDutyCycle4 As Byte                                               ' Holds the 8-bit duty cycle value for the MIBAM channel 4 pin
    Dim MIBAM_bDutyCycle5 As Byte                                               ' Holds the 8-bit duty cycle value for the MIBAM channel 5 pin
    Dim MIBAM_bDutyCycle6 As Byte                                               ' Holds the 8-bit duty cycle value for the MIBAM channel 6 pin
    Dim MIBAM_bDutyCycle7 As Byte                                               ' Holds the 8-bit duty cycle value for the MIBAM channel 7 pin

    Dim MIBAM_bState      As Byte Access
    Dim MIBAM_bBitPos     As Byte Access                                        ' Start from the first element (0) of MIBAM_bBitTable table
    Dim MIBAM_bTemp       As Byte Access
    Dim MIBAM_wMsCounter  As Word Access
    Dim MIBAM_tReady      As Bit
    Dim MIBAM_tDelFlag    As Bit
    Dim MIBAM_bBitTable   As Flash8 = 1, 2, 4, 8, 16, 32, 64, 128               ' Create the MIBAM table in flash memory

    Symbol cMIBAM_Bits = SizeOf(MIBAM_bBitTable)                                ' This is the number of table elements that are used in MIBAM_bBitTable: 8 elements for 256 PWM levels
    Symbol cMIBAM_DelayTime = 255
    Symbol cMIBAM_DelayTimeX2 = (cMIBAM_DelayTime * 2)

'----------------------------------------------------------------------------------------
' The main program starts here
' Cycle the duty cycles of all 8 MIBAM pins
'
Main:
    Setup()                                                                     ' Setup the program

    MIBAM_bDutyCycle0 = 1
    MIBAM_bDutyCycle1 = 2
    MIBAM_bDutyCycle2 = 4
    MIBAM_bDutyCycle3 = 8
    MIBAM_bDutyCycle4 = 16
    MIBAM_bDutyCycle5 = 32
    MIBAM_bDutyCycle6 = 64
    MIBAM_bDutyCycle7 = 128

    Do                                                                          ' Create a loop
        If MIBAM_tReady = True Then                                             ' Is it OK to slter the duty cycles?
            MIBAM_tReady = False                                                ' Yes. So reset the flag
            Inc MIBAM_bDutyCycle0                                               ' Increase the Duty Cycle for MIBAM_Pin0
            Inc MIBAM_bDutyCycle1                                               ' Increase the Duty Cycle for MIBAM_Pin1
            Inc MIBAM_bDutyCycle2                                               ' Increase the Duty Cycle for MIBAM_Pin2
            Inc MIBAM_bDutyCycle3                                               ' Increase the Duty Cycle for MIBAM_Pin3
            Inc MIBAM_bDutyCycle4                                               ' Increase the Duty Cycle for MIBAM_Pin4
            Inc MIBAM_bDutyCycle5                                               ' Increase the Duty Cycle for MIBAM_Pin5
            Inc MIBAM_bDutyCycle6                                               ' Increase the Duty Cycle for MIBAM_Pin6
            Inc MIBAM_bDutyCycle7                                               ' Increase the Duty Cycle for MIBAM_Pin7

            HRsoutLn "MIBAM_bDutyCycle0 = ", Dec MIBAM_bDutyCycle0

            If MIBAM_bDutyCycle0 = 255 Then MIBAM_bDutyCycle0 = 0
            If MIBAM_bDutyCycle1 = 255 Then MIBAM_bDutyCycle1 = 0
            If MIBAM_bDutyCycle2 = 255 Then MIBAM_bDutyCycle2 = 0
            If MIBAM_bDutyCycle3 = 255 Then MIBAM_bDutyCycle3 = 0
            If MIBAM_bDutyCycle4 = 255 Then MIBAM_bDutyCycle4 = 0
            If MIBAM_bDutyCycle5 = 255 Then MIBAM_bDutyCycle5 = 0
            If MIBAM_bDutyCycle6 = 255 Then MIBAM_bDutyCycle6 = 0
            If MIBAM_bDutyCycle7 = 255 Then MIBAM_bDutyCycle7 = 0
        EndIf
        DelayMS 1000                                                            ' Create a large delay so the duty cycles can be seen altering on an oscilloscope
    Loop                                                                        ' Do it forever

'----------------------------------------------------------------------------------------
' Setup the program
' Input     : None
' Output    : None
' Notes     : None
'
Proc Setup()
    Osc_Int64MHz()                                                              ' Setup the device to operate at 64MHz with its internal oscillator
'
' Set all the BAM pins used to output low mode
'
$ifdef MIBAM_Pin0                                                               ' Has MIBAM_Pin0 been $defined?
    PinLow MIBAM_Pin0                                                           ' Yes. So set it as an output low
$endif
$ifdef MIBAM_Pin1                                                               ' Has MIBAM_Pin1 been $defined?
    PinLow MIBAM_Pin1                                                           ' Yes. So set it as an output low
$endif
$ifdef MIBAM_Pin2                                                               ' Has MIBAM_Pin2 been $defined?
    PinLow MIBAM_Pin2                                                           ' Yes. So set it as an output low
$endif
$ifdef MIBAM_Pin3                                                               ' Has MIBAM_Pin3 been $defined?
    PinLow MIBAM_Pin3                                                           ' Yes. So set it as an output low
$endif
$ifdef MIBAM_Pin4                                                               ' Has MIBAM_Pin4 been $defined?
    PinLow MIBAM_Pin4                                                           ' Yes. So set it as an output low
$endif
$ifdef MIBAM_Pin5                                                               ' Has MIBAM_Pin5 been $defined?
    PinLow MIBAM_Pin5                                                           ' Yes. So set it as an output low
$endif
$ifdef MIBAM_Pin6                                                               ' Has MIBAM_Pin6 been $defined?
    PinLow MIBAM_Pin6                                                           ' Yes. So set it as an output low
$endif
$ifdef MIBAM_Pin7                                                               ' Has MIBAM_Pin7 been $defined?
    PinLow MIBAM_Pin7                                                           ' Yes. So set it as an output low
$endif

    MIBAM_bDutyCycle0 = 0
    MIBAM_bDutyCycle1 = 0
    MIBAM_bDutyCycle2 = 0
    MIBAM_bDutyCycle3 = 0
    MIBAM_bDutyCycle4 = 0
    MIBAM_bDutyCycle5 = 0
    MIBAM_bDutyCycle6 = 0
    MIBAM_bDutyCycle7 = 0

    MIBAM_tReady = False
    MIBAM_tDelFlag = False
    MIBAM_bState = 0
    MIBAM_bBitPos = 0                                                           ' Start from the 1st bit (0) of MIBAM_bBitTable table
    MIBAM_wMsCounter = cMIBAM_DelayTime

    Timer0_Setup()                                                              ' Setup Timer0
    IntGlobal_Enable()                                                          ' Enable global interrupts
EndProc

'----------------------------------------------------------------------------------------
' Setup Timer0
' Input     : None
' Output    : None
' Notes     : Sets up Timer0 of an overflow interrupt
'
Proc Timer0_Setup()
    T0CON = 0                                                                   ' Clear the T0CON SFR before altering its bits
    Timer0_Prescaler(Timer0_cPrescaler)                                         ' Set the prescaler value for Timer0
    MIBAM_wTimer0 = Timer0_cValue                                               ' Load the value for a specific overflow time
    Timer0_FlagClear()                                                          ' Clear the Timer0 interrupt flag
    Timer0_IntEnable()                                                          ' Enable a Timer0 interrupt
    Timer0_Start()                                                              ' Start Timer0
EndProc

'----------------------------------------------------------------------------------------
' Set the PIC18F26K22 device to use the internal oscillator at 64MHz
' Input     : None
' Output    : None
' Notes     : Waits for the 4xPLL to become stable
'
Proc Osc_Int64MHz()
    OSCCON = %01111100                                                          ' Setup for the internal 16MHz oscillator
    PLL_Enable()                                                                ' Enable 4xPLL
    Repeat: Until PLL_ReadyFlag() = 1                                           ' Wait for the PLL to become stable
EndProc

'----------------------------------------------------------------------------------------
' Interrupt Handler
' Input     : None
' Output    : MIBAM_tReady holds 1 if the duty cycles have been completed (must be reset in the main program)
' Notes     : Handles a Timer0 overflow interrupt
'
ISR_Handler:
    Context Save                                                                ' Save any compiler system variables and SFRs used within the interrupt handler

    If Timer0_Flag() = True Then                                                ' Is the interrupt a Timer0 overflow?
        If MIBAM_bState = 0 Then                                                ' Yes. So when MIBAM_bState = 0, set the duty cycle state bits
            $ifdef MIBAM_Pin0                                                   ' Has MIBAM_Pin0 been $defined?
                MIBAM_bTemp = MIBAM_bDutyCycle0 >> MIBAM_bBitPos                ' \
                MIBAM_Pin0 = MIBAM_bTemp.0                                      ' / Yes. So set its bit pattern
            $endif
            $ifdef MIBAM_Pin1                                                   ' Has MIBAM_Pin1 been $defined?
                MIBAM_bTemp = MIBAM_bDutyCycle1 >> MIBAM_bBitPos                ' \
                MIBAM_Pin1 = MIBAM_bTemp.0                                      ' / Yes. So set its bit pattern
            $endif
            $ifdef MIBAM_Pin2                                                   ' Has MIBAM_Pin2 been $defined?
                MIBAM_bTemp = MIBAM_bDutyCycle2 >> MIBAM_bBitPos                ' \
                MIBAM_Pin2 = MIBAM_bTemp.0                                      ' / Yes. So set its bit pattern
            $endif
            $ifdef MIBAM_Pin3                                                   ' Has MIBAM_Pin3 been $defined?
                MIBAM_bTemp = MIBAM_bDutyCycle3 >> MIBAM_bBitPos                ' \
                MIBAM_Pin3 = MIBAM_bTemp.0                                      ' / Yes. So set its bit pattern
            $endif
            $ifdef MIBAM_Pin4                                                   ' Has MIBAM_Pin4 been $defined?
                MIBAM_bTemp = MIBAM_bDutyCycle4 >> MIBAM_bBitPos                ' \
                MIBAM_Pin4 = MIBAM_bTemp.0                                      ' / Yes. So set its bit pattern
            $endif
            $ifdef MIBAM_Pin5                                                   ' Has MIBAM_Pin5 been $defined?
                MIBAM_bTemp = MIBAM_bDutyCycle5 >> MIBAM_bBitPos                ' \
                MIBAM_Pin5 = MIBAM_bTemp.0                                      ' / Yes. So set its bit pattern
            $endif
            $ifdef MIBAM_Pin6                                                   ' Has MIBAM_Pin6 been $defined?
                MIBAM_bTemp = MIBAM_bDutyCycle6 >> MIBAM_bBitPos                ' \
                MIBAM_Pin6 = MIBAM_bTemp.0                                      ' / Yes. So set its bit pattern
            $endif
            $ifdef MIBAM_Pin7                                                   ' Has MIBAM_Pin7 been $defined?
                MIBAM_bTemp = MIBAM_bDutyCycle7 >> MIBAM_bBitPos                ' \
                MIBAM_Pin7 = MIBAM_bTemp.0                                      ' / Yes. So set its bit pattern
            $endif

            MIBAM_bState = Cread8 MIBAM_bBitTable[MIBAM_bBitPos]                ' Read the state from the flash memory table
            If MIBAM_tDelFlag = False Then
                Inc MIBAM_bBitPos
            Else
                Dec MIBAM_bBitPos
            EndIf
            MIBAM_bBitPos = MIBAM_bBitPos // cMIBAM_Bits
        EndIf
        Dec MIBAM_bState                                                        ' Countdown the time that the LED will remain in the same state
        Dec MIBAM_wMsCounter

        If MIBAM_wMsCounter = 0 Then
            MIBAM_tDelFlag = False                                              ' Total delay = cMIBAM_DelayTime / ((Fosc / 4) / Prescaler) / 256 msec
            MIBAM_tReady = True                                                 ' This flag is set when cMIBAM_DelayTime msec have elapsed
            MIBAM_wMsCounter = cMIBAM_DelayTimeX2
            MIBAM_bBitPos = 0

        ElseIf MIBAM_wMsCounter = cMIBAM_DelayTime Then
            MIBAM_tDelFlag = True
            MIBAM_bBitPos = cMIBAM_Bits - 1
        EndIf
        MIBAM_wTimer0 = Timer0_cValue                                           ' Re-load TMR0L\H with the value to give a specified overflow time
        Timer0_FlagClear()                                                      ' Clear the Timer0 interrupt flag
    EndIf
    Context Restore                                                             ' Restore any compiler system variables and SFRs used within the interrupt handler, and exit the interrupt

'----------------------------------------------------------------------------------------
' Setup the 18F26K22 device's fuse configs to use its internal oscillator
'
Config_Start
    FOSC = INTIO67                          ' Internal oscillator block
    PLLCFG = Off                            ' Oscillator used directly
    PRICLKEN = On                           ' Primary clock enabled
    FCMEN = Off                             ' Fail-Safe Clock Monitor disabled
    IESO = Off                              ' Oscillator Switchover mode disabled
    PWRTEN = On                             ' Power up timer enabled
    BOREN = Off                             ' Brown-out Reset disabled in hardware and software
    BORV = 190                              ' VBOR set to 1.90 V nominal
    WDTEN = Off                             ' Disable the watchdog timer
    WDTPS = 256                             ' Watchdog window is 1:256
    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 = Off                            ' HFINTOSC output and ready status are not delayed by the oscillator stable status
    T3CMX = PORTB5                          ' T3CKI is on RB5
    P2BMX = PORTB5                          ' P2B is on RB5
    MCLRE = INTMCLR                         ' RE3 input pin enabled.  MCLR 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
    Debug = Off                             ' Disabled
    Cp0 = On                                ' Block 0 (000800-003FFF) code-protected
    CP1 = On                                ' Block 1 (004000-007FFF) code-protected
    CP2 = On                                ' Block 2 (008000-00BFFF) code-protected
    CP3 = On                                ' Block 3 (00C000-00FFFF) code-protected
    CPB = On                                ' Boot block (000000-0007FF) code-protected
    CPD = Off                               ' Data EEPROM not code-protected
    WRT0 = On                               ' Block 0 (000800-003FFF) write-protected
    WRT1 = On                               ' Block 1 (004000-007FFF) write-protected
    WRT2 = On                               ' Block 2 (008000-00BFFF) write-protected
    WRT3 = On                               ' Block 3 (00C000-00FFFF) write-protected
    WRTC = On                               ' Configuration registers (300000-3000FF) write-protected
    WRTB = On                               ' Boot Block (000000-0007FF) write-protected
    WRTD = Off                              ' Data EEPROM not write-protected
    EBTR0 = Off                             ' Block 0 (000800-003FFF) not protected from table reads executed in other blocks
    EBTR1 = Off                             ' Block 1 (004000-007FFF) not protected from table reads executed in other blocks
    EBTR2 = Off                             ' Block 2 (008000-00BFFF) not protected from table reads executed in other blocks
    EBTR3 = Off                             ' Block 3 (00C000-00FFFF) not protected from table reads executed in other blocks
    EBTRB = Off                             ' Boot Block (000000-0007FF) not protected from table reads executed in other blocks
Config_End

The program will alter the duty cycles of 8 pins independently, and if LEDs are attached to them, they will gradually increase in luminence.

Below is a screenshot of the above program listing operating in the Proteus simulator. The MIBAM.bas program and the Proteus simulator files are also attached below:

MIBAM_Simulator.jpg