Positron8 - Experimental Flames Simulator using WS2812B RGB LED chips.

Started by top204, Oct 30, 2024, 04:09 PM

Previous topic - Next topic

top204

Below is an 'experimental' program listing I have created that simulates flames using pseudo random noise and WS2812B RGB LEDs for the flame colours of red to yellow.

When I look at the LEDs, I can see it is not quite what I had in mind for the flame movements, but looking at the workshop's wall when the lights are off and the simulator is on, it does look like a flame of some sort, and is a starting place for further experiments.

In fact, and this is the absolute truth... Last night I asked my wife to come and see what I had done, and through the workshop door's frosted glass, the simulation could be seen working. I then said, unknowingly, "That looks like a fire doesn't it?", and she started to panic and said "but Emmy is in there, get her out!" (Emmy being one of our three lovely cats who lies behind me on my chair). I had forgotten to tell my wife it was a simulation using lights and she thought I was stating that I thought there was a fire in the workshop, so I apologised to her and calmed her down by showing her the LEDs flickering on my desk. :-)

'
'   /\\\\\\\\\
'  /\\\///////\\\
'  \/\\\     \/\\\                                                 /\\\          /\\\
'   \/\\\\\\\\\\\/        /\\\\\     /\\\\\\\\\\     /\\\\\\\\   /\\\\\\\\\\\  /\\\\\\\\\\\  /\\\\\\\\\
'    \/\\\//////\\\      /\\\///\\\  \/\\\//////    /\\\/////\\\ \////\\\////  \////\\\////  \////////\\\
'     \/\\\    \//\\\    /\\\  \//\\\ \/\\\\\\\\\\  /\\\\\\\\\\\     \/\\\         \/\\\        /\\\\\\\\\\
'      \/\\\     \//\\\  \//\\\  /\\\  \////////\\\ \//\\///////      \/\\\ /\\     \/\\\ /\\   /\\\/////\\\
'       \/\\\      \//\\\  \///\\\\\/    /\\\\\\\\\\  \//\\\\\\\\\\    \//\\\\\      \//\\\\\   \//\\\\\\\\/\\
'        \///        \///     \/////     \//////////    \//////////      \/////        \/////     \////////\//
'                                  Let's find out together what makes a PIC Tick!
'
' Experimental WS2812B RGB interface to imitate a flame using pseudo random noise.
'
' Written by Les Johnson for the Positron8 BASIC compiler.
'
    Device = 18F26K40                                       ' Tell the compiler what device to compile for
    Declare Xtal = 64                                       ' Tell the compiler what frequency the device is operating at (in MHz)
    On_Hardware_Interrupt GoTo ISR_Handler                  ' Point the device to the interrupt handler routine

    Declare Auto_Heap_Arrays = On                           ' Tell the compiler to always create arrays above the address of standard variables
    Declare Auto_Variable_Bank_Cross = On                   ' Tell the compiler to makes sure multi-byte variables always reside in the same RAM bank
'
' Setup USART1
'
    Declare Hserial1_Baud = 9600
    Declare HRSOut1_Pin = PORTC.6

$define WS2812B_Pin PORTC.0                                 ' The pin used for the WS2812B chips on a strip
$define WS2812B_Amount 26                                   ' The amount of WS2812B chips to control
    Include "WS2812B.inc"                                   ' Load the RGB WS2812B routines into the program

'------------------------------------------------------------------------------------------------
' General interrupt meta-macros for a PIC18F26K40 device
'
$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 SFRs and flag meta-macros for a PIC18F26K40 device
'
$define Timer0_Int_Bit PIE0bits_TMR0IE                      ' The interrupt enable/disable bit for Timer0
$define Timer0_Flag() PIR0bits_TMR0IF                       ' Timer0 Flag
$define Timer0_FlagClear()  PIR0bits_TMR0IF = 0             ' Clear the Timer0 interrupt flag
$define Timer0_Enable() T0CON0bits_T0EN = 1                 ' Start Timer0
$define Timer0_Disable()  T0CON0bits_T0EN = 0               ' Stop Timer0
$define Timer0_Start() T0CON0bits_T0EN = 1                  ' Start Timer0
$define Timer0_Stop()  T0CON0bits_T0EN = 0                  ' Stop Timer0
$define Timer0_IntEnable()  Timer0_Int_Bit = 1              ' Enable a Timer0 interrupt
$define Timer0_IntDisable() Timer0_Int_Bit = 0              ' Disable a Timer0 interrupt

'------------------------------------------------------------------------------------------------
' Prescaler values for the Timer0_cPrescalerValue meta-macros for a PIC18F26K40 device
'
$define cTimer0_PreScale_1_1     1
$define cTimer0_PreScale_1_2     2
$define cTimer0_PreScale_1_4     3
$define cTimer0_PreScale_1_8     4
$define cTimer0_PreScale_1_16    16
$define cTimer0_PreScale_1_32    32
$define cTimer0_PreScale_1_64    64
$define cTimer0_PreScale_1_128   128
$define cTimer0_PreScale_1_256   256
$define cTimer0_PreScale_1_512   512
$define cTimer0_PreScale_1_1024  1024
$define cTimer0_PreScale_1_2048  2048
$define cTimer0_PreScale_1_4096  4096
$define cTimer0_PreScale_1_8192  8192
$define cTimer0_PreScale_1_16384 16384
$define cTimer0_PreScale_1_32768 32768

'---------------------------------------------------------------------------------
' Calculate the value to place into the TMR0L\H registers in order to achieve a certain overflow interrupt rate (in us)
' The calculation will also work for other timers
'
    $define Timer0_cFOSC $eval (_xtal / 4)                  ' The fOSC of the microcontroller device (_xtal / 4)
    $define Timer0_cPrescalerValue cTimer0_PreScale_1_256   ' The prescaler used for the timer (must be the same prescaler as used for the timer's setup)
    $define Timer0_cMicroSeconds 1000                       ' Interrupt rate (in uS)
    $define Timer0_cTweakValue -10                          ' Holds a tweak value for the timer calculation

    $define Timer0_cValue $eval ((65536 + Timer0_cTweakValue) - ((Timer0_cMicroSeconds / Timer0_cPrescalerValue) * Timer0_cFOSC))

$if Timer0_cPrescalerValue >= Timer0_cMicroSeconds
    $error "Timer0_cPrescalerValue is too large for the value in Timer0_cMicroSeconds"
$endif
$if Timer0_cValue > 65534
    $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

'------------------------------------------------------------------------------
' Alter the bits for a required Timer0 prescaler on a PIC18F26K40 device
' Input     : pPrescaler holds the prescaler value required. i.e. 1, 2, 4, 8, 16, 32, 64, 128, 256, 512 etc...
' Output    : None
' Notes     : Alters the prescaler bits within the Timer0 control SFR
'
$define Timer0_Prescaler(pPrescaler)'
    $if pPrescaler = 1              '
        T0CON1bits_T0CKPS0 = 0      '
        T0CON1bits_T0CKPS1 = 0      '
        T0CON1bits_T0CKPS2 = 0      '
        T0CON1bits_T0CKPS3 = 0      '
    $elseif pPrescaler = 2          '
        T0CON1bits_T0CKPS0 = 1      '
        T0CON1bits_T0CKPS1 = 0      '
        T0CON1bits_T0CKPS2 = 0      '
        T0CON1bits_T0CKPS3 = 0      '
    $elseif pPrescaler = 4          '
        T0CON1bits_T0CKPS0 = 0      '
        T0CON1bits_T0CKPS1 = 1      '
        T0CON1bits_T0CKPS2 = 0      '
        T0CON1bits_T0CKPS3 = 0      '
    $elseif pPrescaler = 8          '
        T0CON1bits_T0CKPS0 = 1      '
        T0CON1bits_T0CKPS1 = 1      '
        T0CON1bits_T0CKPS2 = 0      '
        T0CON1bits_T0CKPS3 = 0      '
    $elseif pPrescaler = 16         '
        T0CON1bits_T0CKPS0 = 0      '
        T0CON1bits_T0CKPS1 = 0      '
        T0CON1bits_T0CKPS2 = 1      '
        T0CON1bits_T0CKPS3 = 0      '
    $elseif pPrescaler = 32         '
        T0CON1bits_T0CKPS0 = 1      '
        T0CON1bits_T0CKPS1 = 0      '
        T0CON1bits_T0CKPS2 = 1      '
        T0CON1bits_T0CKPS3 = 0      '
    $elseif pPrescaler = 64         '
        T0CON1bits_T0CKPS0 = 0      '
        T0CON1bits_T0CKPS1 = 1      '
        T0CON1bits_T0CKPS2 = 1      '
        T0CON1bits_T0CKPS3 = 0      '
    $elseif pPrescaler = 128        '
        T0CON1bits_T0CKPS0 = 1      '
        T0CON1bits_T0CKPS1 = 1      '
        T0CON1bits_T0CKPS2 = 1      '
        T0CON1bits_T0CKPS3 = 0      '
    $elseif pPrescaler = 256        '
        T0CON1bits_T0CKPS0 = 0      '
        T0CON1bits_T0CKPS1 = 0      '
        T0CON1bits_T0CKPS2 = 0      '
        T0CON1bits_T0CKPS3 = 1      '
    $elseif pPrescaler = 512        '
        T0CON1bits_T0CKPS0 = 1      '
        T0CON1bits_T0CKPS1 = 0      '
        T0CON1bits_T0CKPS2 = 0      '
        T0CON1bits_T0CKPS3 = 1      '
    $elseif pPrescaler = 1024       '
        T0CON1bits_T0CKPS0 = 0      '
        T0CON1bits_T0CKPS1 = 1      '
        T0CON1bits_T0CKPS2 = 0      '
        T0CON1bits_T0CKPS3 = 1      '
    $elseif pPrescaler = 2048       '
        T0CON1bits_T0CKPS0 = 1      '
        T0CON1bits_T0CKPS1 = 1      '
        T0CON1bits_T0CKPS2 = 0      '
        T0CON1bits_T0CKPS3 = 1      '
    $elseif pPrescaler = 4096       '
        T0CON1bits_T0CKPS0 = 0      '
        T0CON1bits_T0CKPS1 = 0      '
        T0CON1bits_T0CKPS2 = 1      '
        T0CON1bits_T0CKPS3 = 1      '
    $elseif pPrescaler = 8192       '
        T0CON1bits_T0CKPS0 = 1      '
        T0CON1bits_T0CKPS1 = 0      '
        T0CON1bits_T0CKPS2 = 1      '
        T0CON1bits_T0CKPS3 = 1      '
    $elseif pPrescaler = 16384      '
        T0CON1bits_T0CKPS0 = 0      '
        T0CON1bits_T0CKPS1 = 1      '
        T0CON1bits_T0CKPS2 = 1      '
        T0CON1bits_T0CKPS3 = 1      '
    $elseif pPrescaler = 32768      '
        T0CON1bits_T0CKPS0 = 1      '
        T0CON1bits_T0CKPS1 = 1      '
        T0CON1bits_T0CKPS2 = 1      '
        T0CON1bits_T0CKPS3 = 1      '
    $else                           '
        $error "Unknown Timer0 prescaler value. Supported values for this device are 1 to 32768 in powers of 2" '
    $endif
'
' The variation in yellow colour to create the fire effect
' Define the interval where the color can change
'
    Symbol cLED_Min_Variation = 2.0
    Symbol cLED_Max_Variation = 40.0
    Symbol cLED_MinMax_Variation = (cLED_Max_Variation - cLED_Min_Variation)
'
' If an LED is never to be completely off, put cLED_Min_Intensity to 0.1
' Values must be between 0 & 1
'
    Symbol cLED_Min_Intensity = 0.1
    Symbol cLED_Max_Intensity = 0.7 '1.0
    Symbol cLED_MinMax_Intensity = (cLED_Max_Intensity - cLED_Min_Intensity)
'
' Speed for variations. Higher values is slower operation
'
    Symbol cLED_Speed_Colour = 82.0
    Symbol cLED_Speed_Intensity = 3.0
'
' Create global variables here
'
    Dim Global_lMilliSeconds As Long Access                     ' Holds the millisecond count in the Timer0 interrupt
    Dim LED_wColoursArray[WS2812B_Amount] As Word Heap          ' Holds the values for the red and green LEDs within the WS2812B chips
    Dim wTimer0_SFR As TMR0L.Word                               ' Combine SFRs TMR0L and TMR0H into a 16-bit SFR
   
'---------------------------------------------------------------------------------------------------
' The main program starts here
' Create the random colours, and illuminate the WS2812B LEDs to imitate a flame
'
Main:
    Setup()                                                     ' Setup the program and any peripherals
    Do                                                          ' Create a loop
        Render_LEDs()                                           ' Illuminate the WS2812B LEDs for the flame effect
        DelayMS Random // 50                                    ' Create a pseudo random delay between the flame colour movements
    Loop                                                        ' Do it forever

'---------------------------------------------------------------------------------------------------
' Setup the program and any peripherals
' Input     : None
' Output    : None
' Notes     : None
'
Proc Setup()
    Oscillator_64MHz()                                          ' Set the device to operate at 64MHz using the internal oscillator
    IntGlobal_Disable()                                         ' Disable global interrupts (just to make sure)
    WS2812B_Finish()                                            ' Send a reset pulse to the WS2812B LEDs (just to make sure)
   
    Global_lMilliSeconds = 32757                                ' Set the millisecond counter to a value above 0
    Timer0_Init()                                               ' Initialise Timer0 for a 1ms interval
    Timer0_IntEnable()                                          ' Enable a Timer0 overflow interrupt
    IntGlobal_Enable()                                          ' Enable global interrupts
EndProc

'--------------------------------------------------------------------------
' Initialise Timer0 for a PIC18F26K40 device
' Input     : None
' Output    : None
' Notes     : Timer0 is configured for 16-bit, and enabled
'
Proc Timer0_Init()
    wTimer0_SFR = Timer0_cValue                                 ' Set TMR0L\H for the interrupt duration
    Timer0_FlagClear()                                          ' Clear the Timer0 interrupt flag
    T0CON0 = %00010000                                          ' Prescaler is 1:1. Timer0 disabled. 16-bit operation
    T0CON1 = %01010010                                          ' FOSC/4. Postscaler is 1:1. Not synchronised
    Timer0_Prescaler(Timer0_cPrescalerValue)                    ' Set the prescaler to the value in the preprocessor calculation above
    Timer0_Start()                                              ' Start Timer0
EndProc

'-------------------------------------------------------------------------------------------------
' Combine the 8-bit red and green values into a 16-bit value
' Input     : pRed holds the 8-bit red value
'           : pGreen holds the 8-bit green value
' Output    : Returns the values as a 16-bit integer
' Notes     : None
'
Proc Combine_RedGreen(pRed As Byte, pGreen As Byte), Word
    Result.Byte0 = pRed
    Result.Byte1 = pGreen
EndProc

'-------------------------------------------------------------------------------------------------
' Illuminate the WS2812B LEDs based upon pseudo random noise
' Input     : None
' Output    : None
' Notes     : Makses the colours of the LEDs, shades of red to yellow
'
Proc Render_LEDs()
    Dim bIndex      As Byte
    Dim fRed_Temp   As Float
    Dim fIntense_Temp As Float 
    Dim fRed        As Float
    Dim fGreen      As Float
    Dim bRed        As fRed.Byte0
    Dim bGreen      As fGreen.Byte0
    Dim fColour     As Float
    Dim fIntensity  As Float
    Dim wTime       As Word = Millis16()
    Symbol cRedMax  = 255.0
   
    For bIndex = 0 To WS2812B_Amount - 1
        '
        ' Adjust the multiply and divide values to alter the effect
        '
        fColour    = NoiseF(bIndex * 250.0, (wTime + bIndex) / cLED_Speed_Colour)
        fIntensity = NoiseF(bIndex * 500.0, (wTime + bIndex) / cLED_Speed_Intensity)
        '
        ' The easing function of the random noise can be changed here
        ' Used to avoid a linear effect and give a more natural curve
        ' Can be changed to calling the "QuadraticEase" procedure
        '
        fRed_Temp     = SineEase(fColour / 255.0)
        fIntense_Temp = SineEase(fIntensity / 255.0)

        'fIntense_Temp = (cLED_MinMax_Intensity * fRed_Temp) + cLED_Min_Intensity
        fRed = fIntense_Temp * (cRedMax * fRed_Temp)
        fGreen = fIntense_Temp * ((cLED_MinMax_Variation) * fRed_Temp + cLED_Min_Variation)
       
        bRed = fRed                                                 ' \ Convert the red and green value sinto integers
        bGreen = fGreen                                             ' /
        If bRed > 200 Then bRed = 200                               ' \
        If bRed < 32 Then bRed = 32                                 ' | Make sure the LEDs do not get too bright or too dim
        If bGreen > 200 Then bGreen = 200                           ' |
        If bGreen < 32 Then bGreen = 32                             ' /
       
        If bGreen >= bRed Then                                      ' \
            If bRed > 128 Then                                      ' |
                bGreen = bRed - 80                                  ' |
            Else                                                    ' | Make sure the yellow light is formed and not just green
                bGreen = 0                                          ' |
            EndIf                                                   ' |
        EndIf                                                       ' /
        LED_wColoursArray[bIndex] = Combine_RedGreen(bRed, bGreen)  ' Add the red and green values to the array
    Next
    LEDs_Update()                                                   ' Update the LEDs with the flame colours
EndProc

'-------------------------------------------------------------------------------------------------
' Perform a Quadratic noise easing function
' Input     : pValue holds the value to perform the calculation on
' Output    : Returns the result of the calculation
' Notes     : None
'
Proc QuadraticEase(pValue As Float), Float
    If pValue < 0.5 Then
        Result = (2 * pValue) * pValue
    Else
        Result = ((-2 * pValue) * pValue) + (4 * pValue) - 1
    EndIf
EndProc

'-------------------------------------------------------------------------------------------------
' Perform a Sine based noise easing function
' Input     : pValue holds the value to perform the calculation on
' Output    : Returns the result of the calculation
' Notes     : None
'
Proc SineEase(pValue As Float), Float
    Symbol cM_PI_2 = (3.14159265 / 2.0)   
    Result = Sin(pValue * cM_PI_2)
EndProc

'---------------------------------------------------------------------------------------------------
' Illuminate all the WS2812B LEDs on a line of them
' Input     : Global variable: "LED_wColoursArray" holds the LED colours
'           : Global constant: "cWS2812B_IndexAmount" holds the amount of WS2812B units
' Output    : None
' Notes     : None
'
Proc LEDs_Update()
    Dim bChip As Byte
    Dim lTemp As Long

    IntGlobal_Disable()                         ' Disable global interrupts
    For bChip = cWS2812B_IndexAmount DownTo 0
        lTemp = LED_wColoursArray[bChip]
        WS2812B_Colour(bChip, lTemp.Byte0, lTemp.Byte1, 0)
    Next
    IntGlobal_Enable()                          ' Re-Enable global interrupts
EndProc

'-------------------------------------------------------------------------------------
' Generate a smoother floating point pseudo random noise value with min and max thresholds
' Input     : pMin holds the minimum noise value allowed to be generated
'           : pMax holds the maximum noise value allowed to be generated
' Output    : Returns a floating point pseudo random noise value
' Notes     : Noise is based on a sine wave for smoother transitions
'
Proc NoiseF(pMin As Float, pMax As Float), Float
Static Dim fPhaseShift As Float = 0.0
Static Dim fPhaseSpeed As Float = 0.01
    Dim fNoiseA     As Float
    Dim fNoiseB     As Float
    Dim fInterNoise As Float
    Dim fFraction   As Float
    Dim fTemp       As fInterNoise

    If pMax <= pMin Then ExitProc                                               ' Make sure the max value is not less than the min value                                   
    Do                                                                          ' Create a loop
        fNoiseA = (Random // 64000) + 1024                                      ' \ Create two pseudo random values that are never 0
        fNoiseB = (Random // 64000) + 1024                                      ' /
        '
        ' Calculate fFraction based on a sine wave for smoother transitions
        '
        fPhaseShift = fPhaseShift + fPhaseSpeed
        If fPhaseShift >= 1.0 Then
            fPhaseShift = 0.0
        EndIf
        fTemp = fPhaseShift * 3.1415                                            ' \
        fTemp = fTemp * 2.0                                                     ' | Sine wave modulation for smooth transition
        fFraction = Sin(fTemp) + 1.0                                            ' |
        fFraction = fFraction / 2.0                                             ' /

        fInterNoise = fNoiseA * (1.0 - fFraction)                               ' \ Interpolate between two noise values
        fInterNoise = fInterNoise + (fNoiseB * fFraction)                       ' /
       
        Result = pMin + ((pMax - pMin) * (fInterNoise / 65535.0))               ' Scale the value into the desired range
        Result = fAbs(Result)                                                   ' Make sure it is a positive value
        Select Result                                                           ' \
            Case pMin To pMax                                                   ' | Exit the loop if the noise value is between the pMin and pMax parameters
                Break                                                           ' |
        EndSelect                                                               ' /
    Loop                                                                        ' Continue generating noise until it is within the valid range
EndProc

'---------------------------------------------------------------------------------------------
' Read the milliseconds counter generated by an interrupt
' Input     : None
' Output    : Returns a 16-bit integer value based upon milliseconds
' Notes     : Uses a timer interrupt to increment the global variable "Global_lMilliSeconds"
'
Proc Millis16(), Global_lMilliSeconds.Word0
    Timer0_Stop()                                               ' Stop Timer0 
    If Global_lMilliSeconds > 65535 Then                        ' Has the millisecond timer gone over its maximum?
        Global_lMilliSeconds = 16384                            ' Yes. So set it to a value greater then 0
    EndIf
    Result = Global_lMilliSeconds                               ' Return the value held in Global_lMilliSeconds
    Timer0_Start()                                              ' Re-Start Timer0
EndProc

'---------------------------------------------------------------------------------------------
' Set the PIC18F26K40 to 64MHz operation using its internal oscillator
' Input     : None
' Output    : None
' Notes     : None
'
Proc Oscillator_64MHz()
    OSCCON1 = %01100000
    OSCCON3 = %00000000
    OSCEN   = %00000000
    OSCFRQ  = %00001000                                         ' Set for 64MHz
    OSCTUNE = %00000000
    DelayMS 100                                                 ' Wait for the PLL to stabilise
EndProc

'---------------------------------------------------------------------------------------------
' Interrupt Handler
' Input     : None
' Output    : Global variable "Global_lMilliSeconds" holds the amount of milliseconds counted since first powered up
' Notes     : Interrupts on a Timer0 overflow
'
ISR_Handler:
    Context Save                                                ' Save any compiler system variables and relevant SFRs

    If Timer0_Int_Bit = 1 Then                                  ' Is a Timer0 interrupt enabled?
        If Timer0_Flag() = 1 Then                               ' Yes. So has the Timer0 overflow flag been set?
            Inc Global_lMilliSeconds                            ' Yes. So increment the millisecond variable           
            wTimer0_SFR = Timer0_cValue                         ' Set TMR0L\H for the interrupt duration
            Timer0_FlagClear()                                  ' Clear the Timer0 interrupt flag
        EndIf
    EndIf

    Context Restore                                             ' Restore any compiler system variables and relevant SFRs, and exit the interrupt
   
'---------------------------------------------------------------------------------------------
' Setup the config fuses to use the internal oscillator on a PIC18F26K40 device
' OSC pins are general purpose I/O lines
'
Config_Start
    RSTOSC = HFINTOSC_1MHZ          ' With HFFRQ = 4MHz and CDIV = 4:1
    FEXTOSC = Off                   ' External Oscillator not enabled
    CLKOUTEN = Off                  ' CLKOUT function is disabled
    CSWEN = On                      ' Writing to NOSC and NDIV is allowed
    FCMEN = Off                     ' Fail-Safe Clock Monitor disabled
    MCLRE = EXTMCLR                 ' MCLR pin is MCLR
    PWRTE = On                      ' Power up timer enabled
    LPBOREN = off                   ' LPBOREN disabled
    BOREN = On                        ' Brown-out Reset enabled and controlled by software (BORCONbits_SBOREN is enabled)
    BORV = VBOR_285                 ' Brown-out Reset Voltage set to 2.85V
    ZCD = Off                       ' ZCD disabled. ZCD can be enabled by setting the ZCDSEN bit of ZCDCON
    PPS1WAY = Off                   ' PPSLOCK bit can be set and cleared repeatedly (subject to the unlock sequence)
    STVREN = Off                    ' Stack full/underflow will not cause Reset
    Debug = Off                     ' Background debugger disabled
    XINST = Off                     ' Extended Instruction Set disabled
    SCANE = Off                     ' Scanner module is not available for use. SCANMD bit is ignored
    LVP = Off                       ' Low Voltage programming disabled
    WDTE = Off                      ' WDT disabled
    WDTCPS   = WDTCPS_17            ' Watchdog Divider ratio 1:4194304 (128 seconds)
    WDTCWS   = WDTCWS_7             ' Window always open (100%). Software control. Keyed access not required
    WDTCCS   = LFINTOSC             ' WDT input clock selector->WDT reference clock is the 31.2kHz HFINTOSC output
    WRT0 = Off                      ' Block 0 (000800-001FFF) not write-protected
    WRT1 = Off                      ' Block 1 (002000-003FFF) not write-protected
    WRTC = Off                      ' Configuration registers (300000-30000B) not write-protected
    WRTB = Off                      ' Boot Block (000000-0007FF) write-protected
    WRTD = Off                      ' Data EEPROM not write-protected
    Cp = Off                        ' User NVM code protection disabled
    CPD = Off                       ' Data NVM code protection disabled
    EBTR0 = Off                     ' Block 0 (000800-001FFF) not protected from table reads executed in other blocks
    EBTR1 = Off                     ' Block 1 (002000-003FFF) 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 code is written for a PIC18F26K40 device, but it only uses a Timer0 peripheral on it, so it could easily be adapted to other devices, and even though the code listing looks long, and uses quite a bit of floating point, it only uses 4400 words of flash memory and 267 bytes of RAM, so it could be adapted further and placed on smaller devices if required. :-)

I'll see if I an get a movie of the flame simulation using 26 WS2812B LEDs in a ring, and attach it.

top204

Below is a quick movie of the flames simulator running on my office desk:


I think it is running a bit fast for the flame flickers, but slowing them down is not as easy as it sounds without making it just look like ramping LEDs, so I'll get back into the code ASAP.

diebobo

Don't let that running during the night with a open window for the neighbours to see :D

Colin G3YHV

Very good Les
Looks like a Chinese Lithium battery fire !

top204

Below is a newer version of the flame simulator.

The code does not use any peripherals or interrupts, and can easily be adapted to be used on an enhanced 14-bit core device or 18F device that runs fast enough to control WS2812B RGB LED chips:

'
'   /\\\\\\\\\
'  /\\\///////\\\
'  \/\\\     \/\\\                                                 /\\\          /\\\
'   \/\\\\\\\\\\\/        /\\\\\     /\\\\\\\\\\     /\\\\\\\\   /\\\\\\\\\\\  /\\\\\\\\\\\  /\\\\\\\\\
'    \/\\\//////\\\      /\\\///\\\  \/\\\//////    /\\\/////\\\ \////\\\////  \////\\\////  \////////\\\
'     \/\\\    \//\\\    /\\\  \//\\\ \/\\\\\\\\\\  /\\\\\\\\\\\     \/\\\         \/\\\        /\\\\\\\\\\
'      \/\\\     \//\\\  \//\\\  /\\\  \////////\\\ \//\\///////      \/\\\ /\\     \/\\\ /\\   /\\\/////\\\
'       \/\\\      \//\\\  \///\\\\\/    /\\\\\\\\\\  \//\\\\\\\\\\    \//\\\\\      \//\\\\\   \//\\\\\\\\/\\
'        \///        \///     \/////     \//////////    \//////////      \/////        \/////     \////////\//
'                                  Let's find out together what makes a PIC Tick!
'
' Experimental WS2812B RGB interface to imitate a flame using pseudo random noise.
'
' Written by Les Johnson for the Positron8 BASIC compiler.
'
' Version 4.0.
' Does not use a Timer0 interrupt anymore for the millisecond timer.
' It uses an iterations counter that gets incremented everytime a flame scan is performed.
'
    Device = 18F26K40                                               ' Tell the compiler what device to compile for
    Declare Xtal = 64                                               ' Tell the compiler what frequency the device is operating at (in MHz)
    Declare Auto_Heap_Arrays = On                                   ' Tell the compiler to always create arrays above the address of standard variables
    Declare Auto_Variable_Bank_Cross = On                           ' Tell the compiler to makes sure multi-byte variables always reside in the same RAM bank

$define WS2812B_Pin PORTC.0                                         ' The pin used for the WS2812B chips on a strip
$define WS2812B_Amount 26                                           ' The amount of WS2812B chips to control
    Include "WS2812B.inc"                                           ' Load the RGB WS2812B routines into the program
'
' The variation in yellow colour to create the fire effect
' Define the interval where the color can change
'
    Symbol cLED_Min_Variation = 2.0
    Symbol cLED_Max_Variation = 10.0
    Symbol cLED_MinMax_Variation = (cLED_Max_Variation - cLED_Min_Variation)
'
' If an LED is never to be completely off, put cLED_Min_Intensity to 0.1
' Values must be between 0.0 and 1.0
'
    Symbol cLED_Min_Intensity = 0.1
    Symbol cLED_Max_Intensity = 1.0
    Symbol cLED_MinMax_Intensity = (cLED_Max_Intensity - cLED_Min_Intensity)
'
' Speed for variations. Higher values mean slower operation
'
    Symbol cLED_Speed_Colour = 120.0
    Symbol cLED_Speed_Intensity = 1.0
'
' Create global variables here
'
    Dim Global_wIterations As Word                                  ' Holds the iterations count
    Dim LED_wColoursArray[WS2812B_Amount] As Word Heap              ' Holds the values for the red and green LEDs within the WS2812B chips
    Dim wTimer0_SFR As TMR0L.Word                                   ' Combine SFRs TMR0L and TMR0H into a 16-bit SFR

'---------------------------------------------------------------------------------------------------
' The main program starts here
' Create the random red and yellow colours, and illuminates the WS2812B LEDs to imitate a flame
'
Main:
    Setup()                                                         ' Setup the program and any peripherals
    Do                                                              ' Create a loop
        LEDs_Render()                                               ' Illuminate the WS2812B LEDs for the flame effect
        DelayMS Rand16(30, 100)                                     ' Create a pseudo random delay between the flame colour movements
    Loop                                                            ' Do it forever

'---------------------------------------------------------------------------------------------------
' Setup the program and any peripherals
' Input     :
' Output    : None
' Notes     : None
'
Proc Setup()
    Clear LED_wColoursArray                                         ' Clear the flame colour array
    WS2812B_Finish()                                                ' Send a reset pulse to the WS2812B LEDs (just to make sure)
    Global_wIterations = 16384                                      ' Set the iterations counter to a value above 0
EndProc

'-------------------------------------------------------------------------------------------------
' Combine the 8-bit red and green values into a 16-bit value
' Input     : pRed holds the 8-bit red value
'           : pGreen holds the 8-bit green value
' Output    : Returns the values as a 16-bit integer
' Notes     : None
'
Proc Combine_RedGreen(pRed As Byte, pGreen As Byte), Word
    Dim wRand As Word

    If pRed < 16 Then pRed = 16                                     ' Make sure the red LEDs do not get too dim
    If pGreen < 16 Then pGreen = 16                                 ' Make sure the green LEDs do not get too dim
    If pGreen >= pRed Then                                          ' \
        If pRed > 128 Then                                          ' |
            pGreen = pRed - 80                                      ' |
        Else                                                        ' | Make sure the yellow light is formed and not just green
            pGreen = 0                                              ' |
        EndIf                                                       ' |
    EndIf                                                           ' /
    wRand = Rand16(0, 65535)                                        ' Create a pseudo random value between 0 and 65535
    If wRand < 10000 Or wRand > 16384 Then                          ' Is the random value between the two comparison values?
        pGreen = 0                                                  ' No. So make the LEDs all red, and no yellow
    EndIf
    Result.Byte0 = pRed
    Result.Byte1 = pGreen
EndProc

'-------------------------------------------------------------------------------------------------
' Illuminate the WS2812B LEDs based upon pseudo random noise
' Input     : None
' Output    : None
' Notes     : Makes the colours of the LEDs, shades of red to yellow
'
Proc LEDs_Render()
    Dim bIndex        As Byte
    Dim fRed_Temp     As Float
    Dim fIntense_Temp As Float
    Dim fRed          As Float
    Dim fGreen        As Float
    Dim fColour       As Float
    Dim fIntensity    As Float
    Dim wTime         As Word = Iterations16()
    Dim bRand         As Byte = Rand16(0, 255)
    Symbol cRedMax    = 255.0

    bIndex = 0
    Repeat
        '
        ' Adjust the cLED_Speed_Colour and cLED_Speed_Intensity constant values to alter the effect
        '
        fColour    = NoiseF(bIndex * 250.0, (wTime + bIndex) / cLED_Speed_Colour)
        fIntensity = NoiseF(bIndex * 500.0, (wTime + bIndex) / cLED_Speed_Intensity)
        '
        ' The easing function of the random noise can be changed here
        ' Used to avoid a linear effect and give a more natural curve
        ' Can be changed to calling the "QuadraticEase" procedure
        '
        fRed_Temp = QuadraticEase(fColour / 255.0)      
        If bRand.0 = 1 Then
            fIntense_Temp = QuadraticEase(fIntensity / 255.0)
        Else
            fIntense_Temp = (cLED_MinMax_Intensity * fRed_Temp) + cLED_Min_Intensity 
        EndIf
        fRed = fIntense_Temp * (cRedMax * fRed_Temp)
        fGreen = fIntense_Temp * ((cLED_MinMax_Variation * fRed_Temp) + cLED_Min_Variation)
        LED_wColoursArray[bIndex] = Combine_RedGreen(fRed, fGreen)              ' Add the combined red and green values to the array
        bIndex = bIndex + Rand16(1, 16)                                         ' Increment the index by a random value
    Until bIndex > cWS2812B_IndexAmount                                         ' Loop until all bIndex is greater than the amount of LEDs
    LEDs_Update()                                                               ' Update the LEDs with the flame colours
EndProc

'-------------------------------------------------------------------------------------------------
' Perform a Quadratic noise easing function
' Input     : pValue holds the value to perform the calculation on
' Output    : Returns the result of the calculation
' Notes     : None
'
Proc QuadraticEase(pValue As Float), Float
    If pValue < 0.5 Then
        Result = (2.0 * pValue) * pValue
    Else
        Result = ((-2.0 * pValue) * pValue) + (4.0 * pValue) - 1.0
    EndIf
EndProc

'---------------------------------------------------------------------------------------------------
' Illuminate all the WS2812B LEDs on a line of them
' Input     : Global variable: "LED_wColoursArray" holds the LED colours
'           : Global constant: "cWS2812B_IndexAmount" holds the amount of WS2812B units
' Output    : None
' Notes     : None
'
Proc LEDs_Update()
    Dim bChip As Byte
    Dim wTemp As Word

    For bChip = cWS2812B_IndexAmount DownTo 0                               ' Create a loop for the amount of WS2812B chips
        wTemp = LED_wColoursArray[bChip]                                    ' Read the flame colours array
        WS2812B_Colour(bChip, wTemp.Byte0, wTemp.Byte1, 0)                  ' Alter the Red and Green LEDs only in a WS2812B chip
    Next                                                                    ' Close the loop
EndProc

'-------------------------------------------------------------------------------------
' Generate a smoother floating point pseudo random noise value with min and max thresholds
' Input     : pMin holds the minimum noise value allowed to be generated
'           : pMax holds the maximum noise value allowed to be generated
' Output    : Returns a floating point pseudo random noise value
' Notes     : Noise is based on a sine wave for smoother transitions
'
Proc NoiseF(pMin As Float, pMax As Float), Float
Static Dim fPhaseShift As Float = 0.0
Static Dim fPhaseSpeed As Float = 0.01
    Dim fNoiseA     As Float
    Dim fNoiseB     As Float
    Dim fInterNoise As Float
    Dim fFraction   As Float
    Dim fTemp       As fInterNoise

    If pMax <= pMin Then ExitProc                                               ' Make sure the max value is not less than the min value
    Do                                                                          ' Create a loop
        fNoiseA = Rand16(1024, 64000)                                           ' \ Create two pseudo random integer values that are never 0
        fNoiseB = Rand16(1024, 64000)                                           ' /
        '
        ' Calculate fFraction based on a sine wave for smoother transitions
        '
        fPhaseShift = fPhaseShift + fPhaseSpeed
        If fPhaseShift >= 1.0 Then
            fPhaseShift = 0.0
        EndIf
        fTemp = fPhaseShift * 3.1415                                            ' \
        fTemp = fTemp * 2.0                                                     ' | Sine wave modulation for smooth transition
        fFraction = Sin(fTemp) + 1.0                                            ' |
        fFraction = fFraction / 2.0                                             ' /

        fInterNoise = fNoiseA * (1.0 - fFraction)                               ' \ Interpolate between two noise values
        fInterNoise = fInterNoise + (fNoiseB * fFraction)                       ' /

        Result = pMin + ((pMax - pMin) * (fInterNoise / 65535.0))               ' Scale the value into the desired range
        Result = fAbs(Result)                                                   ' Make sure it is a positive value
        Select Result                                                           ' \
            Case pMin To pMax                                                   ' | Exit the loop if the noise value is between the pMin and pMax parameters
                Break                                                           ' |
        EndSelect                                                               ' /
    Loop                                                                        ' Continue generating noise until it is within the valid range
EndProc

'---------------------------------------------------------------------------------------------
' Create a pseudo random unsigned 16-bit integer value
' Input     : pMin holds the minimum value for the pseudo random value
'           : pMax hold the maximum value for the pseudo random value
' Output    : Returns the pseudo random integer value
' Notes     : None
'
Proc Rand16(pMin As Word, pMax As Word), Word
    Result = ((Random) // (pMax - pMin)) + pMin
EndProc

'---------------------------------------------------------------------------------------------
' Increment the iterations counter
' Input     : None
' Output    : Returns a 16-bit integer value based upon the value of Global_wIterations
' Notes     : None
'
Proc Iterations16(), Global_wIterations.Word0
    If Global_wIterations >= 65535 Then                                         ' Has the iterations counter gone over its maximum?
        Global_wIterations = 16384                                              ' Yes. So set it to a value greater then 0
    EndIf
    Inc Global_wIterations                                                      ' Increment the iterations counter
    Result = Global_wIterations                                                 ' Return the value held in Global_wIterations
EndProc

'---------------------------------------------------------------------------------------------
' Setup the config fuses to use the internal oscillator on a PIC18F26K40 at 64MHz
' OSC pins are general purpose I/O lines
'
Config_Start
    RSTOSC = HFINTOSC_64MHZ                 ' Internal oscillator at 64MHz and CDIV = 1:1
    FEXTOSC = Off                           ' External oscillator not enabled
    CLKOUTEN = Off                          ' CLKOUT function is disabled
    CSWEN = On                              ' Writing to NOSC and NDIV is allowed
    FCMEN = Off                             ' Fail-Safe Clock Monitor disabled
    MCLRE = EXTMCLR                         ' MCLR pin is MCLR
    PWRTE = On                              ' Power up timer enabled
    LPBOREN = off                           ' LPBOREN disabled
    BOREN = On                                ' Brown-out Reset enabled and controlled by software (BORCONbits_SBOREN is enabled)
    BORV = VBOR_285                         ' Brown-out Reset Voltage set to 2.85V
    ZCD = Off                               ' ZCD disabled. ZCD can be enabled by setting the ZCDSEN bit of ZCDCON
    PPS1WAY = Off                           ' PPSLOCK bit can be set and cleared repeatedly
    STVREN = Off                            ' Stack full/underflow will not cause Reset
    Debug = Off                             ' Background debugger disabled
    XINST = Off                             ' Extended Instruction Set disabled
    SCANE = Off                             ' Scanner module is not available for use. SCANMD bit is ignored
    LVP = Off                               ' Low Voltage programming disabled
    WDTE = Off                              ' Watchdog timer disabled
    WDTCPS = WDTCPS_17                      ' Watchdog Divider ratio 1:4194304 (128 seconds)
    WDTCWS = WDTCWS_7                       ' Watchdog Window always open (100%). Software control. Keyed access not required
    WDTCCS = LFINTOSC                       ' Watchdog timer reference clock is the 31.2kHz HFINTOSC output
    WRT0 = Off                              ' Block 0 (000800-001FFF) not write-protected
    WRT1 = Off                              ' Block 1 (002000-003FFF) not write-protected
    WRTC = Off                              ' Configuration registers (300000-30000B) not write-protected
    WRTB = Off                              ' Boot Block (000000-0007FF) write-protected
    WRTD = Off                              ' Data EEPROM not write-protected
    Cp = Off                                ' User NVM code protection disabled
    CPD = Off                               ' Data NVM code protection disabled
    EBTR0 = Off                             ' Block 0 (000800-001FFF) not protected from table reads executed in other blocks
    EBTR1 = Off                             ' Block 1 (002000-003FFF) 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 simulation runs slower and more gentle, with the red flames flickering, with intermittent yellow flames for a short while.