2015-04-12 09:59 AM
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 > 0) ? 255 : (r < 0.0) ? 0 : (uint8_t)r;
colourOut[1] = (g > 0) ? 255 : (g < 0.0) ? 0 : (uint8_t)g;
colourOut[2] = (b > 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) / 0f);
neopixel_Send((
const
uint8_t(*)[]) &colourbuf, 1);
Delay(100000L);
}
}
}
}
2015-06-30 08:35 AM
Hey,
did you get this Project working? I'm working on the same topic, but this Project witch works fine with the StdPeriphLib doesn't work at all with the HAL Driver. I don't even get the one cycle of data transfer to work. Greeding Alex2015-07-01 06:38 AM
You'll see this symptom if you forget to clear the interrupt bits (DMA_LIFCR et am.) before starting the new operation.
Observed behavior is that the EN bit immediately clears without the DMA actually doing anything.