AnsweredAssumed Answered

STM32F411 - DMA only works once

Question asked by Phil Pemberton on Apr 12, 2015
Latest reply on Jul 1, 2015 by mckenney.bruce
Hi,

I'm trying to use an STM32F411 Nucleo (Nucleo-F411RE) to drive a few Adafruit Neopixel LEDs (also known by the part number WS2812). These use a digital pseudo-PWM control signal to transfer digital data to a control chip built into the LED.

I've figured out how to make the STM32Cube HAL program the timer in PWM+DMA mode, and it generates a 24-pulse train successfully, however this only works once. On every subsequent attempt, it locks up on the call to HAL_DMA_PollForTransfer; the DMA transfer never completes (or perhaps never starts).

To put it another way, I get the output "DMA kick... done" once, but on the second run through neopixel_Send(), I only get "DMA kick...".

Can anyone offer any suggestions?

I've attached my code.

#include <math.h>
 
#include "diag/Trace.h"
 
#include "stm32f4xx.h"
#include "stm32f4xx_hal.h"
#include "stm32f4xx_hal_cortex.h"
 
void Delay(__IO uint32_t nCount) {
  while(nCount--) {
  }
}
 
// PWM timer declarations
#define PWM_TIMER           TIM3
#define PWM_TIMER_CHANNEL   TIM_CHANNEL_1
#define PWM_TIMER_DMAID     TIM_DMA_ID_CC1
 
// Timer period
#define TIM_PERIOD          29
// PWM width for a high bit
#define TIM_COMPARE_HIGH    18
// PWM width for a low bit
#define TIM_COMPARE_LOW     9
 
TIM_HandleTypeDef timHandle;
DMA_HandleTypeDef dmaHandle;
 
 
/* Buffer that holds one complete DMA transmission
 *
 * Ensure that this buffer is big enough to hold
 * all data bytes that need to be sent
 *
 * The buffer size can be calculated as follows:
 * number of LEDs * 24 bytes + 42 bytes
 *
 * This leaves us with a maximum string length of
 * (2^16 bytes per DMA stream - 42 bytes)/24 bytes per LED = 2728 LEDs
 */
uint16_t LED_BYTE_Buffer[100];
 
// FIXME: This does more than initialise TIMER 3
void neopixel_Init(void)
{
    /* GPIOB Configuration: PWM as alternate function push-pull */
    // "D5" output on Nucleo, PB4, TIM3 channel 1
    __GPIOB_CLK_ENABLE();
    GPIO_InitTypeDef gpioInit;
    gpioInit.Pin = GPIO_PIN_4;
    gpioInit.Mode = GPIO_MODE_AF_PP;
    gpioInit.Speed = GPIO_SPEED_HIGH;
    gpioInit.Alternate = GPIO_AF2_TIM3;
    gpioInit.Pull = GPIO_NOPULL;
    HAL_GPIO_Init(GPIOB, &gpioInit);
 
 
    // Set up the timer
    __TIM3_CLK_ENABLE();
    timHandle.Instance = PWM_TIMER;
    timHandle.Init.ClockDivision = 0;
    timHandle.Init.Prescaler = (((HAL_RCC_GetPCLK1Freq() * 2) / TIM_PERIOD) / 800000) - 1;  // 800kHz tick
    timHandle.Init.Period = TIM_PERIOD;
    timHandle.Init.CounterMode = TIM_COUNTERMODE_UP;
    trace_printf("PCLK1 %d\n", HAL_RCC_GetPCLK1Freq());
    trace_printf("Prescaler %d\n", timHandle.Init.Prescaler);
    trace_printf("Period %d\n", timHandle.Init.Period);
    trace_printf("HAL_TIM_PWM_Init: %d\n", HAL_TIM_PWM_Init(&timHandle));
 
    /* PWM1 Mode configuration: Channel 1 */
    TIM_OC_InitTypeDef sConfig;
    sConfig.OCMode = TIM_OCMODE_PWM1;
    sConfig.OCPolarity = TIM_OCPOLARITY_HIGH;
    sConfig.OCNPolarity = TIM_OCNPOLARITY_HIGH;
    sConfig.OCIdleState = TIM_OCIDLESTATE_RESET;
    sConfig.OCNIdleState = TIM_OCNIDLESTATE_RESET;
    sConfig.OCFastMode = TIM_OCFAST_ENABLE;     // FIXME what does this do?
    sConfig.Pulse = 0;      // Pulse is the pulse width, between 0 and the PWM period
    trace_printf("HAL_TIM_PWM_ConfigChannel: %d\n", HAL_TIM_PWM_ConfigChannel(&timHandle, &sConfig, PWM_TIMER_CHANNEL));
 
#ifdef PWM_TEST_MODE
    // hang here, use PWM only at half rate for testing
    trace_printf("Test mode, testing PWM output without DMA...\n");
    sConfig.Pulse = TIM_PERIOD / 2;
    trace_printf("HAL_TIM_PWM_ConfigChannel: %d\n", HAL_TIM_PWM_ConfigChannel(&timHandle, &sConfig, PWM_TIMER_CHANNEL));
    HAL_TIM_PWM_Start(&timHandle, PWM_TIMER_CHANNEL);
    __HAL_TIM_ENABLE(&timHandle);
    for (;;);
#endif
 
    // DMA handle configuration
    // DMA1 CH5 STR4 =>
    __DMA1_CLK_ENABLE();
    dmaHandle.Instance = DMA1_Stream4;
 
    //DMA_InitStructure.DMA_BufferSize = 42;
 
    dmaHandle.Init.Channel = DMA_CHANNEL_5;
    dmaHandle.Init.Direction = DMA_MEMORY_TO_PERIPH;
    dmaHandle.Init.FIFOMode = DMA_FIFOMODE_DISABLE;
    dmaHandle.Init.FIFOThreshold = DMA_FIFO_THRESHOLD_HALFFULL;
 
    dmaHandle.Init.MemDataAlignment = DMA_MDATAALIGN_HALFWORD;
    dmaHandle.Init.MemInc = DMA_MINC_ENABLE;
    dmaHandle.Init.MemBurst = DMA_MBURST_SINGLE;
 
    dmaHandle.Init.Mode = DMA_NORMAL;
 
    dmaHandle.Init.PeriphDataAlignment = DMA_PDATAALIGN_HALFWORD;
    dmaHandle.Init.PeriphInc = DMA_PINC_DISABLE;
    dmaHandle.Init.PeriphBurst = DMA_PBURST_SINGLE;
 
    dmaHandle.Init.Priority = DMA_PRIORITY_HIGH;
 
    trace_printf("HAL_DMA_Init: %d\n", HAL_DMA_Init(&dmaHandle));
 
    __HAL_LINKDMA(&timHandle, hdma[PWM_TIMER_DMAID], dmaHandle);
 
        /* PWM_TIMER CC1 DMA Request enable */
        //TIM_DMACmd(PWM_TIMER, DMA_SOURCE, ENABLE);
}
 
/* This function sends data bytes out to a string of WS2812s
 * The first argument is a pointer to the first RGB triplet to be sent
 * The seconds argument is the number of LEDs in the chain
 *
 * This will result in the RGB triplet passed by argument 1 being sent to
 * the LED that is the furthest away from the controller (the point where
 * data is injected into the chain)
 */
void neopixel_Send(const uint8_t (*color)[3], const uint16_t _len)
{
    int i, j;
    uint8_t led;
    uint16_t memaddr;
    size_t buffersize;
    uint16_t len = _len;
 
    // Byte order mapping. 0 is red, 1 is green, 2 is blue
    const uint8_t pix_map[3] = {0, 2, 1};
 
    buffersize = (len*24u)+42;  // number of bytes needed is #LEDs * 24 bytes + 42 trailing bytes
    memaddr = 0;                // reset buffer memory index
    led = 0;                    // reset led index
 
    // fill transmit buffer with correct compare values to achieve
    // correct pulse widths according to color values
    while (len)
    {
        for (i = 0; i < 3; i++)
        {
            for (j = 0; j < 8; j++)                 // GREEN data
            {
                if ( (color[led][pix_map[i]]<<j) & 0x80 )   // data sent MSB first, j = 0 is MSB j = 7 is LSB
                {
                    LED_BYTE_Buffer[memaddr] = TIM_COMPARE_HIGH;    // compare value for logical 1
                }
                else
                {
                    LED_BYTE_Buffer[memaddr] = TIM_COMPARE_LOW;     // compare value for logical 0
                }
                memaddr++;
            }
        }
 
        led++;
        len--;
    }
 
    // add needed delay at end of byte cycle, pulsewidth = 0
    while(memaddr < buffersize)
    {
        LED_BYTE_Buffer[memaddr] = 0;
        memaddr++;
    }
 
    // PAP: Clear the timer's counter and set the compare value to 0. This
    // sets the output low on start and gives us a full cycle to set up DMA.
    __HAL_TIM_SET_COUNTER(&timHandle, 0);
    __HAL_TIM_SET_COMPARE(&timHandle, PWM_TIMER_CHANNEL, 0);
 
    // Start the timer and DMA transfer
    HAL_DMA_Init(&dmaHandle);
    HAL_TIM_PWM_Start_DMA(&timHandle, PWM_TIMER_CHANNEL, LED_BYTE_Buffer, buffersize);
    trace_printf("DMA kick... ");
    HAL_DMA_PollForTransfer(&dmaHandle, HAL_DMA_FULL_TRANSFER, HAL_MAX_DELAY);
    trace_printf("done\n");
}
 
 
void blend(const uint8_t *colourA, const uint8_t *colourB, uint8_t *colourOut, float amount)
{
    float r, g, b;
 
    r = ((float)colourB[0] * amount) + ((float)colourA[0] * (1.0f - amount));
    g = ((float)colourB[1] * amount) + ((float)colourA[1] * (1.0f - amount));
    b = ((float)colourB[2] * amount) + ((float)colourA[2] * (1.0f - amount));
 
    colourOut[0] = (r > 255.0) ? 255 : (r < 0.0) ? 0 : (uint8_t)r;
    colourOut[1] = (g > 255.0) ? 255 : (g < 0.0) ? 0 : (uint8_t)g;
    colourOut[2] = (b > 255.0) ? 255 : (b < 0.0) ? 0 : (uint8_t)b;
}
 
const uint8_t off[3]    = {0,0,0};
const uint8_t red[3]    = {255,0,0};
const uint8_t green[3]  = {0,255,0};
const uint8_t blue[3]   = {0,0,255};
const uint8_t white[3]  = {255,255,255};
 
 
int main(void) {
    int i, j;
 
    neopixel_Init();
 
 
    while (1){
        const uint8_t *order[] = {
            red,
            green,
            blue
        };
 
        // Blend colours
        const int MAXJ = (sizeof(order)/sizeof(order[0]));
        for (j = 0; j < MAXJ; j++) {
            for (i=0; i<100; i++) {
                uint8_t colourbuf[3];
                int k = (j == MAXJ-1) ? 0 : j+1;
                blend(order[j], order[k], colourbuf, ((float)i) / 100.0f);
                neopixel_Send((const uint8_t(*)[]) &colourbuf, 1);
                Delay(100000L);
            }
        }
    }
}

Outcomes