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
}
}