Page 4 of 4

Re: PWM and code in the main loop

Posted: Tue Mar 18, 2025 5:37 pm
by julian_lpp
Just added SetPixelColor as to let modify each led individually....
Tested with RGBW (dont have now rgb neopixels)

regards, julian

Code: Select all

#include <Arduino.h>
#include <HardwareTimer.h>

//con o sin white
#define ES_RGBW 1
#define LED_COUNT 7


#if ES_RGBW == 1
    #define BITS_PER_LED 32
    uint8_t leds[LED_COUNT][4];
#else
    #define BITS_PER_LED 24
    uint8_t leds[LED_COUNT][3];
#endif    

#define BUFFER_SIZE (LED_COUNT * BITS_PER_LED + 2)  // allow space for 2 dummy entries

// WS2812 Timing Values (for 72MHz clock)
#define T0H 28  // ~0.35µs high
#define T0L 56  // ~0.9µs low
#define T1H 56  // ~0.9µs high
#define T1L 28  // ~0.35µs low

uint16_t pwmBuffer[BUFFER_SIZE];

// Use TIM4 
HardwareTimer timer(TIM4);

// DMA Interrupt Handler
extern "C" void DMA1_Channel7_IRQHandler();

#if ES_RGBW == 1
    void setColor(uint8_t r, uint8_t g, uint8_t b, uint8_t w);
    void setPixelColor(unsigned int a_pixel_num, uint8_t r, uint8_t g, uint8_t b, uint8_t w);
#else
    void setColor(uint8_t r, uint8_t g, uint8_t b);
    void setPixelColor(uint8_t r, uint8_t g, uint8_t b);
#endif

void sendBuffer();

void setup() {
    Serial.begin(115200);
    delay(1000);
    
    digitalWrite(PB6, LOW);
    pinMode(PB6, OUTPUT);

    // Enable DMA Clock
    RCC->AHBENR |= RCC_AHBENR_DMA1EN;

    // Configure DMA for TIM4 CH1 (PB6)
    /*DMA1_Channel7->CPAR = (uint32_t)&TIM4->CCR1;  Peripheral address (TIM4 Capture Compare Register) 
    Acà se relaciona al DMA con el timer4, para que ante cada lectura de DMA, se llene 
    el registro CCR1 del TIMER 4 con el valor del BUFFER, que es lo que determina que el pin permanece HIGH, 
    una vez que se llega a ese valor, el pin pasa a LOW
    */
    DMA1_Channel7->CPAR = (uint32_t)&TIM4->CCR1; 
    DMA1_Channel7->CMAR = (uint32_t)pwmBuffer;  //"Channel x Memory Address Register" (CMAR) and is used to store the memory address where the DMA will read from or write to.
    DMA1_Channel7->CNDTR = BUFFER_SIZE;         // Number of data items to transfer
    DMA1_Channel7->CCR = DMA_CCR_MINC           // Memory increment mode
                        | DMA_CCR_DIR           // Memory to Peripheral
                        | DMA_CCR_TCIE          // Transfer complete interrupt
                        | DMA_CCR_PSIZE_0       // Peripheral size = 16-bit
                        | DMA_CCR_MSIZE_0       // Memory size = 16-bit
                        | DMA_CCR_PL;           // Priority level

    // Enable DMA Interrupt
    NVIC_EnableIRQ(DMA1_Channel7_IRQn);

    // Configure TIM4 (Now outputs PWM on PB6)
    timer.setPWM(1, PB6, 800000, 0);           // 800kHz. 0% dutycycle = no pulse
    
}

void loop() {
    
    setColor(0, 255, 0, 0); // Green
    sendBuffer();
    delay(500);

    setColor(0, 0, 255, 0); // Blue
    sendBuffer();
    delay(500);

    setColor(255, 0, 0, 0); // red
    sendBuffer();
    delay(500);

    setColor(0, 0, 0, 255); // white
    sendBuffer();
    delay(500);    

    for (int i = 0; i < LED_COUNT; i++) {
        setPixelColor(i, random(255), random(255), random(255), random(255)); 
    }    
    sendBuffer();
    delay(500);

}


#if ES_RGBW == 1
    void setColor(uint8_t r, uint8_t g, uint8_t b, uint8_t w){

        // Fill all LEDs with the same color
        for (int i = 0; i < LED_COUNT; i++) {
            leds[i][0] = g; // WS2812 uses GRB format
            leds[i][1] = r;
            leds[i][2] = b;
            leds[i][3] = w;
        } 

    }

    void setPixelColor(unsigned int a_pixel_index, uint8_t r, uint8_t g, uint8_t b, uint8_t w){
        //
        // Fill all LEDs with the same color
        if (a_pixel_index<LED_COUNT){

            leds[a_pixel_index][0] = g; // WS2812 uses GRB format
            leds[a_pixel_index][1] = r;
            leds[a_pixel_index][2] = b;
            leds[a_pixel_index][3] = w;
        }

    }

    void fillBuffer(){
        // Convert to PWM signal
        int index = 0;
        for (int i = 0; i < LED_COUNT; i++) {
            for (int j = 0; j < 4; j++) {
                for (int bit = 7; bit >= 0; bit--) {
                    if (leds[i][j] & (1 << bit)) {
                        pwmBuffer[index++] = T1H; // High for "1"
                    } else {
                        pwmBuffer[index++] = T0H; // High for "0"
                    }
                }
            }
        }
       pwmBuffer[index++] = 0;            // add dummy pulse
       pwmBuffer[index++] = 0; 
    }




#else
    // Convert RGB to PWM timing buffer
    void setColor(uint8_t r, uint8_t g, uint8_t b, uint8_t a_unused) {
        uint8_t leds[LED_COUNT][3];

        // Fill all LEDs with the same color
        for (int i = 0; i < LED_COUNT; i++) {
            leds[i][0] = g; // WS2812 uses GRB format
            leds[i][1] = r;
            leds[i][2] = b;
        }

        // Convert to PWM signal
        int index = 0;
        for (int i = 0; i < LED_COUNT; i++) {
            for (int j = 0; j < 3; j++) {
                for (int bit = 7; bit >= 0; bit--) {
                    if (leds[i][j] & (1 << bit)) {
                        pwmBuffer[index++] = T1H; // High for "1"
                    } else {
                        pwmBuffer[index++] = T0H; // High for "0"
                    }
                }
            }
        }
        pwmBuffer[index++] = 0;     // add dummy pulse
        pwmBuffer[index++] = 0; 
    }
#endif


// Start DMA Transfer
void sendBuffer() {

    fillBuffer();

    // **Reset Pulse**: Ensure at least 50µs LOW before sending data
    delayMicroseconds(50);                 // 50µs to be safe
    
    TIM4->CR1 &= ~TIM_CR1_CEN;             // ensure TIM4 stopped
    TIM4->DIER |= TIM_DIER_UDE; /*cuando el buffer se transfirio completamente, se deshabilita este bit TIM4->DIER &= ~TIM_DIER_UDE; (ver dma irq handler)*/
    
    // Reset DMA
    /*
    Reset the DMA channel: Before initiating a new DMA transfer, 
    it's often necessary to disable the channel to ensure that it's in a known state.
    */
    DMA1_Channel7->CCR &= ~DMA_CCR_EN;
    DMA1->IFCR |= DMA_IFCR_CHTIF7;         // clear half-transfer interrupt
    DMA1->IFCR |= DMA_IFCR_CTCIF7;         // clear transfer complete interrupt
    DMA1->IFCR |= DMA_IFCR_CTEIF7;         // clear error flag
      
    /*The CNDTR register holds the number of data items that the DMA channel is programmed to transfer. When the DMA transfer starts, 
    this register is loaded with the desired number of data items. As each data item is transferred, the CNDTR register is decremented. 
    When it reaches zero, the DMA transfer is complete.
    */
    DMA1_Channel7->CNDTR = BUFFER_SIZE;

    /*This line of code sets the "Enable" bit in the DMA1 Channel 1 Control Register. In simpler terms, it enables the DMA channel. */
    DMA1_Channel7->CCR |= DMA_CCR_EN;

    /*This enables the timer to generate a DMA request when an update event occurs.*/
    TIM4->CR1 |= TIM_CR1_CEN;             // start TIM4
        
}

// DMA Transfer Complete Interrupt
extern "C" void DMA1_Channel7_IRQHandler(void) {
     /*DMA_ISR_TCIF1 "Transfer Complete Interrupt Flag"....
     entonces con el & queda claro que ese bit del DMA1->ISR està en 1*/

     if (DMA1->ISR & DMA_ISR_TCIF7) { /*// acà se setea el flag de transferencia completa*/
        DMA1->IFCR = DMA_IFCR_CTCIF7;  // Clear only the Transfer Complete flag
        DMA1_Channel7->CCR &= ~DMA_CCR_EN;
        TIM4->CR1 &= ~TIM_CR1_CEN;     // stop TIM4
    }
}