Continue to Site

Welcome to our site!

Electro Tech is an online community (with over 170,000 members) who enjoy talking about and building electronic circuits, projects and gadgets. To participate you need to register. Registration is free. Click here to register now.

  • Welcome to our site! Electro Tech is an online community (with over 170,000 members) who enjoy talking about and building electronic circuits, projects and gadgets. To participate you need to register. Registration is free. Click here to register now.

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.
 

Latest threads

Back
Top