More fun with ws2812 this time XC8 and CLC

be80be

Well-Known Member
Im totally lost here need some pointers

Code:
/**
 * @file main.c
 * @brief WS2812 LED Driver using CLC, PWM, and SPI on PIC18F26Q10
 * @author Gemini
 * @date July 12, 2024
 *
 * @details
 * This program demonstrates a highly efficient method for driving WS2812/NeoPixel
 * LEDs using the hardware peripherals of a PIC18F26Q10 microcontroller.
 * It offloads the precise, timing-critical signal generation from the CPU
 * to the hardware, freeing up the processor for other tasks and preventing
 * timing jitter from interrupts.
 *
 * Hardware Logic:
 * 1. TMR2 + PWM6: Generates the base 'T0H' pulse width (~0.4us) for every bit.
 * 2. TMR2 + PWM7: Generates a "stretcher" pulse, phase-shifted to start after
 * the T0H pulse. This is used to extend the high time for a 'T1H' bit.
 * 3. SPI1: Shifts out the color data (e.g., 24 bits for one LED) at the same
 * frequency as the bit period (800kHz). The SDO line is high for a '1' and
 * low for a '0'.
 * 4. CLC1: Combines these signals with the logic:
 * Output = (PWM6_out) OR (PWM7_out AND SPI1_SDO)
 * - If SDO is 0, output is just the short PWM6 pulse (T0H).
 * - If SDO is 1, output is PWM6 OR'd with PWM7, creating a single, long
 * high pulse (T1H).
 * 5. PPS: Routes the final CLC1 output to a physical pin (RC2).
 *
 * Configuration:
 * - MCU Clock (FOSC): 64 MHz from HFINTOSC
 * - Bit Period: 1.25us (800 kHz), set by TMR2
 * - T0H (logic 0 high time): ~0.406 us
 * - T1H (logic 1 high time): ~0.812 us
 * - WS2812 Data Pin: RC2
 */

//==============================================================================
// CONFIGURATION BITS
//==============================================================================
#pragma config FEXTOSC = OFF    // External Oscillator Selection->Oscillator not enabled
#pragma config RSTOSC = HFINTOSC_64MHZ// Reset Oscillator Selection->HFINTOSC with HFFRQ = 64 MHz and CDIV = 1:1
#pragma config CLKOUTEN = OFF   // Clock out Enable bit->CLKOUT function is disabled
#pragma config PR1WAY = ON      // PRLOCKED One-Way Set Enable bit->PRLOCKED bit can be cleared and set only once
#pragma config CSWEN = ON       // Clock Switch Enable bit->Writing to NOSC and NDIV is allowed
#pragma config FCMEN = ON       // Fail-Safe Clock Monitor Enable bit->Fail-Safe Clock Monitor enabled

#pragma config MCLRE = EXTMCLR  // MCLR Enable bit->MCLR pin enabled, RE3 input pin disabled
#pragma config PWRTS = PWRT_OFF // Power-up timer selection bits->PWRT is disabled
#pragma config MVECEN = ON      // Multi-vector enable bit->Multi-vector enabled, Vector table used for interrupts
#pragma config IVT1WAY = ON     // IVTLOCK bit can be cleared and set only once
#pragma config LPBOREN = OFF    // Low Power BOR Enable bit->ULPBOR disabled
#pragma config BOREN = SBORDIS  // Brown-out Reset Enable bits->Brown-out Reset enabled , SBOREN bit is ignored
#pragma config BORV = VBOR_2P45 // Brown-out Reset Voltage selection bits->Brown-out Reset Voltage (VBOR) set to 2.45V
#pragma config ZCD = OFF        // ZCD Disable bit->ZCD disabled. ZCD can be enabled by setting the ZCDSEN bit of ZCDCON
#pragma config PPS1WAY = ON     // PPSLOCK bit One-Way Set Enable bit->PPSLOCK bit can be cleared and set only once; PPS registers remain locked after one clear/set cycle
#pragma config STVREN = ON      // Stack Full/Underflow Reset Enable bit->Stack full/underflow will cause Reset
#pragma config DEBUG = OFF      // Debugger Enable bit->Background debugger disabled
#pragma config XINST = OFF      // Extended Instruction Set Enable bit->Extended Instruction Set and Indexed Addressing Mode disabled

#pragma config WDTCS = WDTCLK_31KHZ // WDT Clock source select bits->WDT uses LPRC 31kHz for clock
#pragma config WDTE = OFF       // WDT operating mode->WDT Disabled

//==============================================================================
// INCLUDES
//==============================================================================
#include <xc.h>
#include <stdint.h>

//==============================================================================
// DEFINES
//==============================================================================
#define _XTAL_FREQ 64000000UL
#define NUM_LEDS 12 // Define how many LEDs are in your strip

//==============================================================================
// PROTOTYPES
//==============================================================================
void SYSTEM_Initialize(void);
void CLC_Initialize(void);
void PWM_Initialize(void);
void SPI_Initialize(void);
void PINS_Initialize(void);

void ws2812_send_byte(uint8_t data);
void ws2812_send_color(uint8_t r, uint8_t g, uint8_t b);
void ws2812_latch(void);
void ws2812_clear_all(void);

//==============================================================================
// DRIVER FUNCTIONS
//==============================================================================

/**
 * @brief Sends a single byte to the SPI buffer for the CLC to process.
 * @param data The byte to send.
 */
void ws2812_send_byte(uint8_t data) {
    while(!SPI1STATUSbits.TXBE); // Wait for SPI Tx buffer to be empty
    SPI1TXB = data;
}

/**
 * @brief Sends a 24-bit color value (GRB format) for one LED.
 * @param r Red component (0-255)
 * @param g Green component (0-255)
 * @param b Blue component (0-255)
 */
void ws2812_send_color(uint8_t r, uint8_t g, uint8_t b) {
    // WS2812 LEDs expect data in Green-Red-Blue order
    ws2812_send_byte(g);
    ws2812_send_byte(r);
    ws2812_send_byte(b);
}

/**
 * @brief Latches the color data to the LEDs and resets the data line.
 * This function must be called after sending color data for all LEDs.
 */
void ws2812_latch() {
    // Wait for the last byte to finish transmitting completely
    while(!SPI1STATUSbits.SRMT);

    // Disable the CLC. This forces the output pin low, creating the
    // required >50us reset pulse to latch the colors.
    CLC1CONbits.EN = 0;

    // A 100us delay is safe for the reset condition.
    __delay_us(100);

    // Re-enable the CLC for the next data transmission.
    CLC1CONbits.EN = 1;
}

/**
 * @brief Sets all LEDs in the strip to off (black).
 */
void ws2812_clear_all() {
    for (uint8_t i = 0; i < NUM_LEDS; i++) {
        ws2812_send_color(0, 0, 0);
    }
    ws2812_latch();
}


//==============================================================================
// INITIALIZATION ROUTINES
//==============================================================================

/**
 * @brief Initializes all required peripherals by calling their specific init functions.
 */
void SYSTEM_Initialize(void) {
    PINS_Initialize();
    PWM_Initialize();
    SPI_Initialize();
    CLC_Initialize(); // CLC should be last, as it uses other peripheral outputs
}

/**
 * @brief Configures the pins and Peripheral Pin Select (PPS) registers.
 */
void PINS_Initialize(void) {
    // Set RC2 as an output (this will be our WS2812 data pin)
    TRISCbits.TRISC2 = 0;
    LATCbits.LATC2 = 0;

    // Route CLC1 output to RC2 pin
    RC2PPS = 0b00001001; // RC2 <- CLC1OUT
}

/**
 * @brief Configures TMR2, PWM6, and PWM7.
 */
void PWM_Initialize(void) {
    // TMR2 provides the 800kHz (1.25us) period for both PWMs
    T2CON = 0b10000000; // TMR2 ON, 1:1 prescaler, 1:1 postscaler
    PR2 = 19;           // Period = (19+1) * 4 / 64MHz = 1.25us

    // PWM6 generates the base T0H pulse (~0.4us)
    // Duty Cycle = 26 -> (26 * 62.5ns) = 0.406us
    PWM6DCH = 6;
    PWM6DCL = 0b11000000;
    PWM6CON = 0b10000000; // PWM6 enabled, active high

    // PWM7 generates the "stretcher" pulse
    // Same duty cycle as PWM6
    PWM7DCH = 6;
    PWM7DCL = 0b11000000;
    // Phase shift PWM7 to start right after PWM6 ends
    PWM7PHH = 6;
    PWM7PHL = 0b11000000;
    PWM7CON = 0b10000000; // PWM7 enabled, active high

    // Enable and load shadow register values into active registers
    PWMEN |= 0b11000000;  // Enable PWM6 and PWM7
    PWMLOAD |= 0b11000000; // Load buffer values
}

/**
 * @brief Configures the SPI1 peripheral.
 */
void SPI_Initialize(void) {
    // Set SPI1 clock source to FOSC
    SPI1CLK = 0x00;
    // Set Baud Rate to 800kHz to match the PWM period
    // Baud = 64MHz / (2 * (BaudRate + 1)) -> 39 = 64MHz / (2 * 40)
    SPI1BAUD = 39;
    // Master mode, continuous transfers, MSB first
    SPI1CON0 = 0b10000010;
    // SPI Enabled
    SPI1CON1 = 0b10000000;
    // Default settings for CON2
    SPI1CON2 = 0x00;
}

/**
 * @brief Configures CLC1 to combine the PWM and SPI signals.
 */
void CLC_Initialize(void) {
    // --- Input Selection ---
    // Route peripheral outputs to the CLC data inputs
    CLC1SEL0 = 0b00010110; // CLCIN0 <- PWM6_out
    CLC1SEL1 = 0b00010111; // CLCIN1 <- PWM7_out
    CLC1SEL2 = 0b00011000; // CLCIN2 <- SPI1_SDO
    CLC1SEL3 = 0b01000011; // CLCIN3 <- SCK1_CONST1 (not used in logic but good practice)

    // --- Logic Gate Configuration (4-input LUT) ---
    // The logic is: F(D2,D1,D0) = D0 | (D1 & D2)
    // Where D0=PWM6, D1=PWM7, D2=SDO
    // This translates to the truth table value 0b11111010
    CLC1GLS0 = 0xFA;
    CLC1GLS1 = 0x00; // Gate 2 unused
    CLC1GLS2 = 0x00; // Gate 3 unused
    CLC1GLS3 = 0x00; // Gate 4 unused

    // No output inversions needed
    CLC1POL = 0x00;

    // --- CLC Mode and Enable ---
    // Mode '010' = 4-input LUT, output from Gate 1
    // EN bit is set to 1 to enable the CLC
    CLC1CON = 0b10000010;
}

//==============================================================================
// MAIN APPLICATION
//==============================================================================
void main(void) {
    SYSTEM_Initialize();

    // Give the LEDs a moment to power up and stabilize
    __delay_ms(10);
    ws2812_clear_all();
    __delay_ms(10);

    uint8_t led_position = 0;

    while (1) {
        // --- Simple "Knight Rider" / Cylon Animation ---

        // Turn on the current LED in red
        for (uint8_t i = 0; i < NUM_LEDS; i++) {
            if (i == led_position) {
                ws2812_send_color(50, 0, 0); // Set one LED to a dim red
            } else {
                ws2812_send_color(0, 0, 0); // All others are off
            }
        }
        ws2812_latch(); // Send the data

        // Move to the next LED
        led_position++;
        if (led_position >= NUM_LEDS) {
            led_position = 0;
        }

        __delay_ms(50); // Animation speed
    }
}
 
Here's hopefully working code
Code:
/**
 * @file main.c
 * @brief Final WS2812 LED Driver using CLC, SSP, and TMR on PIC18F26Q10
 * @author Gemini
 * @date July 13, 2024
 *
 * @details
 * This program drives WS2812 LEDs using a robust, hardware-only method.
 * It uses the SR-Latch mode of the CLC, which is simpler and less
 * prone to compiler naming issues than the PWM/CCP method.
 *
 * Hardware Logic:
 * 1. TMR2: Sets the bit period to 1.25us (800 kHz). Its output resets the latch.
 * 2. SSP1 (SPI): Runs at 2.4MHz. It sends a 3-bit pattern for each LED data bit.
 * - '1' bit = SPI sends `110`
 * - '0' bit = SPI sends `100`
 * The SDO line sets the latch.
 * 3. CLC1 (SR Latch):
 * - Set (S) input is connected to the SPI Data Out (SDO).
 * - Reset (R) input is connected to the Timer2 Match signal.
 * This combination creates the precise WS2812 waveform on the CLC output pin.
 * 4. PPS: Routes the final CLC1 output to a physical pin (RC2).
 *
 * This version uses legacy register names (SSP) and direct bitmasking to
 * ensure maximum compatibility with all XC8 compiler versions.
 */

//==============================================================================
// CONFIGURATION BITS
//==============================================================================
#pragma config FEXTOSC = OFF
#pragma config RSTOSC = HFINTOSC_64MHZ // IMPORTANT: 64MHz Clock is required
#pragma config CLKOUTEN = OFF
#pragma config CSWEN = ON
#pragma config FCMEN = ON
#pragma config MCLRE = EXTMCLR
#pragma config PWRTE = OFF
#pragma config BOREN = SBORDIS
#pragma config BORV = VBOR_190
#pragma config ZCD = OFF
#pragma config PPS1WAY = ON
#pragma config STVREN = ON
#pragma config WDTE = OFF

//==============================================================================
// INCLUDES
//==============================================================================
#include <xc.h>
#include <stdint.h>
#include <string.h>

//==============================================================================
// DEFINES
//==============================================================================
#define _XTAL_FREQ 64000000UL
#define NUM_LEDS 12 // Define how many LEDs are in your strip

// Buffer to hold the GRB color data for each LED
static uint8_t led_color_data[NUM_LEDS * 3];

// Buffer to hold the encoded SPI data. 3 SPI bytes for each color byte.
static uint8_t spi_data_buffer[NUM_LEDS * 3 * 3];

//==============================================================================
// PROTOTYPES
//==============================================================================
void SYSTEM_Initialize(void);
void PINS_Initialize(void);
void TMR2_Initialize(void);
void SPI_Initialize(void);
void CLC_Initialize(void);

void WS2812_SetPixelColor(uint16_t pixel, uint8_t red, uint8_t green, uint8_t blue);
void WS2812_Show(void);

//==============================================================================
// DRIVER FUNCTIONS
//==============================================================================

void WS2812_SetPixelColor(uint16_t pixel, uint8_t red, uint8_t green, uint8_t blue) {
    if (pixel < NUM_LEDS) {
        // WS2812B LEDs expect data in Green, Red, Blue order
        uint16_t index = pixel * 3;
        led_color_data[index + 0] = green;
        led_color_data[index + 1] = red;
        led_color_data[index + 2] = blue;
    }
}

void WS2812_Show(void) {
    uint16_t spi_buf_idx = 0;
    // These are the 3-bit patterns sent by the SPI for each WS2812B bit.
    // WS2812B '0' bit is SPI pattern 100 (0x4)
    // WS2812B '1' bit is SPI pattern 110 (0x6)
    const uint8_t patterns[] = {0x4, 0x6};

    // Go through the entire GRB color buffer
    for (uint16_t i = 0; i < sizeof(led_color_data); i++) {
        uint8_t color_byte = led_color_data[i];
        uint32_t spi_bits = 0;

        // Convert one 8-bit color value into 24 SPI bits
        for (int bit_index = 7; bit_index >= 0; bit_index--) {
            spi_bits <<= 3;
            spi_bits |= patterns[(color_byte >> bit_index) & 0x01];
        }

        // Unpack the 24 bits into 3 bytes for the SPI buffer
        spi_data_buffer[spi_buf_idx++] = (spi_bits >> 16) & 0xFF;
        spi_data_buffer[spi_buf_idx++] = (spi_bits >> 8) & 0xFF;
        spi_data_buffer[spi_buf_idx++] = spi_bits & 0xFF;
    }

    // Send the data using a blocking write
    for(uint16_t i = 0; i < sizeof(spi_data_buffer); i++) {
        while(!(PIR7 & 0x01)); // Wait for SSP1TXIF to be set
        SSP1BUF = spi_data_buffer[i];
    }
    
    // Wait for the last byte to finish transmitting
    while(SSP1STATbits.BF);

    // A delay of >50us is required to latch the colors
    __delay_us(100);
}


//==============================================================================
// INITIALIZATION ROUTINES
//==============================================================================

void SYSTEM_Initialize(void) {
    PINS_Initialize();
    TMR2_Initialize();
    SPI_Initialize();
    CLC_Initialize();
}

void PINS_Initialize(void) {
    // Set RC2 as an output for the CLC
    TRISCbits.TRISC2 = 0;
    ANSELCbits.ANSELC2 = 0;
    LATCbits.LATC2 = 0;
    RC2PPS = 0b00001001; // RC2 <- CLC1OUT

    // Set RC1 as SDO1 output for SPI
    TRISCbits.TRISC1 = 0;
    ANSELCbits.ANSELC1 = 0;
    RC1PPS = 0b00010111; // RC1 -> SDO1
}

void TMR2_Initialize(void) {
    // FOSC=64MHz, TMR2_CLK=FOSC/4=16MHz, Tick=62.5ns
    // Period = 1.25us (800kHz) -> PR2 = 1.25us / 62.5ns - 1 = 19
    PR2 = 19;
    T2CON = 0b10000000; // TMR2 ON, 1:1 prescaler, 1:1 postscaler
}

void SPI_Initialize(void) {
    // Baud Rate = FOSC / (4 * (SSP1ADD + 1))
    // 2.4MHz = 64MHz / (4 * (SSP1ADD + 1)) -> SSP1ADD = 5.66 -> use 6
    // Actual Baud = 64MHz / (4 * 7) = 2.28MHz (This is close enough)
    SSP1ADD = 6;
    SSP1STAT = 0b01000000; // CKE=1, SMP=0
    SSP1CON1 = 0b00100000; // SSPEN=1, Master Mode
}

void CLC_Initialize(void) {
    // Configure CLC1 as an SR Latch
    // Set Input = SDO1 (SPI Data Out)
    // Reset Input = TMR2
    CLC1SEL0 = 0x17; // CLCIN0 <- SDO1
    CLC1SEL1 = 0x0F; // CLCIN1 <- TMR2_OUT
    
    // Gates are not used in SR Latch mode, but clear them
    CLC1GLS0 = 0x00;
    CLC1GLS1 = 0x00;
    CLC1GLS2 = 0x00;
    CLC1GLS3 = 0x00;
    CLC1POL = 0x00; // No inversions

    // Mode '011' = SR Latch, EN=1 to enable
    CLC1CON = 0b10000011;
}

//==============================================================================
// MAIN APPLICATION
//==============================================================================
void main(void) {
    SYSTEM_Initialize();
    __delay_ms(10);

    // Set all LEDs to off initially
    memset(led_color_data, 0, sizeof(led_color_data));
    WS2812_Show();
    __delay_ms(10);

    uint8_t led_position = 0;

    while (1) {
        // Simple "Knight Rider" / Cylon Animation
        for (uint8_t i = 0; i < NUM_LEDS; i++) {
            if (i == led_position) {
                WS2812_SetPixelColor(i, 50, 0, 0); // Dim red
            } else {
                WS2812_SetPixelColor(i, 0, 0, 0);  // Off
            }
        }
        WS2812_Show();

        led_position++;
        if (led_position >= NUM_LEDS) {
            led_position = 0;
        }

        __delay_ms(50);
    }
}
 
The method I came up with was a function to (You just updated the thread).

Similar to your new post, but no bulk bitshifting.
For each input byte, start with a fixed template output value. (100100100100100etc).

Test each bit in the input byte, if set then set the corresponding bit in the output word.
I used bitmasks for testing and setting, shifted one & three places at each increment.

Using a union & pointer to overlay a 32 bit word over the three [four] bytes in the output buffer makes it directly accessible.
 
Cookies are required to use this site. You must accept them to continue using the site. Learn more…