PWM-DMA signal malformed (STM32F407, WS2812) - Page 1
Forum section: electronics / projects

author: adhoc_user
author_rank: Contributor
timestamp: June 21, 2017, 11:10:19 am
content: Hi folks,i want to run an WS2812 stripe over PWM (data from DMA), but the first LED sometimes flickers. On the oscilloscope you can see a little rising signal after the 6th period. All other ~600 periods are okay. I have tried various timers and DMA streams and channels, every time the same result.Code was partially generated by CubeMX, so the parameters sould be fine.Any suggestions are welcome, what it could be?GPIO:initStructure_GPIO.Pin = GPIO_PIN_5;initStructure_GPIO.Mode = GPIO_MODE_AF_PP;initStructure_GPIO.Pull = GPIO_PULLUP;initStructure_GPIO.Speed = GPIO_SPEED_HIGH;initStructure_GPIO.Alternate = GPIO_AF2_TIM3;HAL_GPIO_Init(GPIOB, &initStructure_GPIO);Timer:__TIM3_CLK_ENABLE();TIM_MasterConfigTypeDef sMasterConfig;TIM_OC_InitTypeDef sConfigOC;handle_TIM.Instance = TIM3;handle_TIM.Channel = HAL_TIM_ACTIVE_CHANNEL_2;handle_TIM.Init.Prescaler = 0;handle_TIM.Init.CounterMode = TIM_COUNTERMODE_UP;handle_TIM.Init.Period = ((SystemCoreClock/2) / Constants::stripe_pwm_speed_hz/1)/1 - 1; // 800kHzhandle_TIM.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;if (HAL_TIM_PWM_Init(&handle_TIM) != HAL_OK){while(1);}sMasterConfig.MasterOutputTrigger = TIM_TRGO_OC2REF;sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE;if (HAL_TIMEx_MasterConfigSynchronization(&handle_TIM, &sMasterConfig) != HAL_OK){while(1);}sConfigOC.OCMode = TIM_OCMODE_PWM1;sConfigOC.Pulse = 0;sConfigOC.OCPolarity = TIM_OCPOLARITY_HIGH;sConfigOC.OCFastMode = TIM_OCFAST_DISABLE;if (HAL_TIM_PWM_ConfigChannel(&handle_TIM, &sConfigOC, TIM_CHANNEL_2) != HAL_OK){while(1);}DMA:__HAL_RCC_DMA1_CLK_ENABLE();/* Peripheral DMA init*/handle_DMA.Instance = DMA1_Stream5;handle_DMA.Init.Channel = DMA_CHANNEL_5;handle_DMA.Init.Direction = DMA_MEMORY_TO_PERIPH;handle_DMA.Init.PeriphInc = DMA_PINC_DISABLE;handle_DMA.Init.MemInc = DMA_MINC_ENABLE;handle_DMA.Init.PeriphDataAlignment = DMA_PDATAALIGN_HALFWORD;handle_DMA.Init.MemDataAlignment = DMA_MDATAALIGN_HALFWORD;handle_DMA.Init.Mode = DMA_NORMAL;handle_DMA.Init.Priority = DMA_PRIORITY_VERY_HIGH;handle_DMA.Init.FIFOMode = DMA_FIFOMODE_DISABLE;if (HAL_DMA_Init(&handle_DMA) != HAL_OK) {while (1);}/* Several peripheral DMA handle pointers point to the same DMA handle.Be aware that there is only one stream to perform all the requested DMAs. */__HAL_LINKDMA(&handle_TIM,hdma,handle_DMA);__HAL_LINKDMA(&handle_TIM,hdma,handle_DMA);HAL_NVIC_SetPriority(DMA1_Stream5_IRQn, 0, 0);HAL_NVIC_EnableIRQ(DMA1_Stream5_IRQn);ISR:extern "C" void DMA1_Stream5_IRQHandler(void){HAL_DMA_IRQHandler(handle_dma);}Starting:const uint32_t led_amount = 24;const uint32_t color_bits_LED = 24;const uint32_t reset_padding = 96;uint32_t pwm_buffer_size = led_amount * color_bits_LED + reset_padding;uint16_t pwm_Buffer[Constants::pwm_buffer_size];... fill all pwm buffer elements with 7 (WS2812 logic 0)...HAL_TIM_PWM_Start_DMA(&handle_TIM, TIM_CHANNEL_2, (uint32_t *) pwm_Buffer, (uint16_t) Constants::pwm_buffer_size);

author: bson
author_rank: Unknown
timestamp: June 21, 2017, 08:20:08 pm
content: What does the interrupt handler do? It looks like the pin momentarily is drifting on the output (with a weak pullup?), which can happen during reconfiguration. It might be related to the strategy used for DMA buffer management, which probably looks something like this:1. Buffer reaches end and the DMA uses the configured buffer start,length to begin the next buffer, then starts outputting it2. The DMA controller issues an interrupt3. The interrupt handler finds the next buffer and configures the DMA for the next buffer start,length4. The interrupt handler returnsIf there's any peripheral reconfiguration during step 3 (because it might just be shared generic "start" code that also performs complete reconfiguration) there could be a momentary I/O dropout. This would also appear sometime after it starts outputting the current buffer as it chains up the next one. Even if it only cyclically outputs the same buffer repeatedly, the DMA controller still needs to be set up for the same one in advance. (I'm not familiar enough with ST peripherals or their libraries... I usually use NXP processors and my own code.)

author: adhoc_user
author_rank: Contributor
timestamp: June 22, 2017, 06:42:56 am
content: Hi and thanks for your answer bson,here is the code for the ISR HAL_DMA_IRQHandler(handle_dma):void HAL_DMA_IRQHandler(DMA_HandleTypeDef *hdma){uint32_t tmpisr;__IO uint32_t count = 0U;uint32_t timeout = SystemCoreClock / 9600U;/* calculate DMA base and stream number */DMA_Base_Registers *regs = (DMA_Base_Registers *)hdma->StreamBaseAddress;tmpisr = regs->ISR;/* Transfer Error Interrupt management ***************************************/if ((tmpisr & (DMA_FLAG_TEIF0_4 << hdma->StreamIndex)) != RESET){if(__HAL_DMA_GET_IT_SOURCE(hdma, DMA_IT_TE) != RESET){/* Disable the transfer error interrupt */hdma->Instance->CR &= ~(DMA_IT_TE);/* Clear the transfer error flag */regs->IFCR = DMA_FLAG_TEIF0_4 << hdma->StreamIndex;/* Update error code */hdma->ErrorCode |= HAL_DMA_ERROR_TE;}}/* FIFO Error Interrupt management ******************************************/if ((tmpisr & (DMA_FLAG_FEIF0_4 << hdma->StreamIndex)) != RESET){if(__HAL_DMA_GET_IT_SOURCE(hdma, DMA_IT_FE) != RESET){/* Clear the FIFO error flag */regs->IFCR = DMA_FLAG_FEIF0_4 << hdma->StreamIndex;/* Update error code */hdma->ErrorCode |= HAL_DMA_ERROR_FE;}}/* Direct Mode Error Interrupt management ***********************************/if ((tmpisr & (DMA_FLAG_DMEIF0_4 << hdma->StreamIndex)) != RESET){if(__HAL_DMA_GET_IT_SOURCE(hdma, DMA_IT_DME) != RESET){/* Clear the direct mode error flag */regs->IFCR = DMA_FLAG_DMEIF0_4 << hdma->StreamIndex;/* Update error code */hdma->ErrorCode |= HAL_DMA_ERROR_DME;}}/* Half Transfer Complete Interrupt management ******************************/if ((tmpisr & (DMA_FLAG_HTIF0_4 << hdma->StreamIndex)) != RESET){if(__HAL_DMA_GET_IT_SOURCE(hdma, DMA_IT_HT) != RESET){/* Clear the half transfer complete flag */regs->IFCR = DMA_FLAG_HTIF0_4 << hdma->StreamIndex;/* Multi_Buffering mode enabled */if(((hdma->Instance->CR) & (uint32_t)(DMA_SxCR_DBM)) != RESET){/* Current memory buffer used is Memory 0 */if((hdma->Instance->CR & DMA_SxCR_CT) == RESET){if(hdma->XferHalfCpltCallback != NULL){/* Half transfer callback */hdma->XferHalfCpltCallback(hdma);}}/* Current memory buffer used is Memory 1 */else{if(hdma->XferM1HalfCpltCallback != NULL){/* Half transfer callback */hdma->XferM1HalfCpltCallback(hdma);}}}else{/* Disable the half transfer interrupt if the DMA mode is not CIRCULAR */if((hdma->Instance->CR & DMA_SxCR_CIRC) == RESET){/* Disable the half transfer interrupt */hdma->Instance->CR &= ~(DMA_IT_HT);}if(hdma->XferHalfCpltCallback != NULL){/* Half transfer callback */hdma->XferHalfCpltCallback(hdma);}}}}/* Transfer Complete Interrupt management ***********************************/if ((tmpisr & (DMA_FLAG_TCIF0_4 << hdma->StreamIndex)) != RESET){if(__HAL_DMA_GET_IT_SOURCE(hdma, DMA_IT_TC) != RESET){/* Clear the transfer complete flag */regs->IFCR = DMA_FLAG_TCIF0_4 << hdma->StreamIndex;if(HAL_DMA_STATE_ABORT == hdma->State){/* Disable all the transfer interrupts */hdma->Instance->CR &= ~(DMA_IT_TC | DMA_IT_TE | DMA_IT_DME);hdma->Instance->FCR &= ~(DMA_IT_FE);if((hdma->XferHalfCpltCallback != NULL) || (hdma->XferM1HalfCpltCallback != NULL)){hdma->Instance->CR &= ~(DMA_IT_HT);}/* Clear all interrupt flags at correct offset within the register */regs->IFCR = 0x3FU << hdma->StreamIndex;/* Process Unlocked */__HAL_UNLOCK(hdma);/* Change the DMA state */hdma->State = HAL_DMA_STATE_READY;if(hdma->XferAbortCallback != NULL){hdma->XferAbortCallback(hdma);}return;}if(((hdma->Instance->CR) & (uint32_t)(DMA_SxCR_DBM)) != RESET){/* Current memory buffer used is Memory 0 */if((hdma->Instance->CR & DMA_SxCR_CT) == RESET){if(hdma->XferM1CpltCallback != NULL){/* Transfer complete Callback for memory1 */hdma->XferM1CpltCallback(hdma);}}/* Current memory buffer used is Memory 1 */else{if(hdma->XferCpltCallback != NULL){/* Transfer complete Callback for memory0 */hdma->XferCpltCallback(hdma);}}}/* Disable the transfer complete interrupt if the DMA mode is not CIRCULAR */else{if((hdma->Instance->CR & DMA_SxCR_CIRC) == RESET){/* Disable the transfer complete interrupt */hdma->Instance->CR &= ~(DMA_IT_TC);/* Process Unlocked */__HAL_UNLOCK(hdma);/* Change the DMA state */hdma->State = HAL_DMA_STATE_READY;}if(hdma->XferCpltCallback != NULL){/* Transfer complete callback */hdma->XferCpltCallback(hdma);}}}}/* manage error case */if(hdma->ErrorCode != HAL_DMA_ERROR_NONE){if((hdma->ErrorCode & HAL_DMA_ERROR_TE) != RESET){hdma->State = HAL_DMA_STATE_ABORT;/* Disable the stream */__HAL_DMA_DISABLE(hdma);do{if (++count > timeout){break;}}while((hdma->Instance->CR & DMA_SxCR_EN) != RESET);/* Process Unlocked */__HAL_UNLOCK(hdma);/* Change the DMA state */hdma->State = HAL_DMA_STATE_READY;}if(hdma->XferErrorCallback != NULL){/* Transfer error callback */hdma->XferErrorCallback(hdma);}}}------------------I have checked via breakpoint and after sending the function goes to the part/* Clear the transfer complete flag */regs->IFCR = DMA_FLAG_TCIF0_4 << hdma->StreamIndex;and/* Disable the transfer complete interrupt if the DMA mode is not CIRCULAR */else{if((hdma->Instance->CR & DMA_SxCR_CIRC) == RESET){/* Disable the transfer complete interrupt */hdma->Instance->CR &= ~(DMA_IT_TC);/* Process Unlocked */__HAL_UNLOCK(hdma);/* Change the DMA state */hdma->State = HAL_DMA_STATE_READY;}if(hdma->XferCpltCallback != NULL){/* Transfer complete callback */hdma->XferCpltCallback(hdma);}I afraid there is no IO reconfiguration after DMA start. I checked all configuration points via debug breakpoints. Only at beginning the GPIO/TIMER and DMA will be configured. After DMA start the data block for the WS2812 will be sent and then after the transmission the IRQ for the transmission complete was generated.In the attachment you can see the logic in the error case. Every period is 2.5µs long. And in the marked area there are 2 high levels (random, not in every data block / but every time after the 6th period), caused by the interference you can see in the first post. This is generating the flickering on the first LED of the WS2812 stripe.

author: Siwastaja
author_rank: Super Contributor
timestamp: June 22, 2017, 06:53:09 am
content: STM32 libraries are a total bloat catastrophe, buggy, too difficult to understand, and require a lot of copypasted (or autogenerated!) boilerplate code to be actually useful at all. And when you use copypasted/autogenerated code, you are using random code that does a random thing, which, in most real-world cases, is something else than you hoped for (unless you are replicating the demo for which the code was originally written). Even if it was the right thing, you need to start from scratch every time you have an issue like this (and it always happens...)Read the reference manual for relevant sections to gain understanding how everything works together, and configure the peripherals yourself, like we are used to do traditionally. STM32 peripherals and documentation are within acceptable limits when you do that. Looking at the libraries gives you the wrong impression that it's much more complex than it actually is.Rewriting without the library whatsoever typically shrinks the number of code lines to about 1/3rd to 1/5th, also increasing readability; which is a good sign that the library is a total joke, as it should be exactly the other way around! Another good sign is that most library use examples need to comment each and every line about what they do.

author: adhoc_user
author_rank: Contributor
timestamp: June 23, 2017, 09:41:20 am
content: Yeah, thats right the HAL from stm is not the first choice.I have compared every single line with register changes. All changes were ok and according to the reference manual the config was fine. After some hours of debugging I used the functionHAL_TIM_PWM_Stop_DMA(htim, TIM_CHANNEL_2)-----------------------------------------------------------Code:HAL_StatusTypeDef HAL_TIM_PWM_Stop_DMA(TIM_HandleTypeDef *htim, uint32_t Channel){/* Check the parameters */assert_param(IS_TIM_CCX_INSTANCE(htim->Instance, Channel));switch (Channel){case TIM_CHANNEL_1:{/* Disable the TIM Capture/Compare 1 DMA request */__HAL_TIM_DISABLE_DMA(htim, TIM_DMA_CC1);}break;case TIM_CHANNEL_2:{/* Disable the TIM Capture/Compare 2 DMA request */__HAL_TIM_DISABLE_DMA(htim, TIM_DMA_CC2);}break;case TIM_CHANNEL_3:{/* Disable the TIM Capture/Compare 3 DMA request */__HAL_TIM_DISABLE_DMA(htim, TIM_DMA_CC3);}break;case TIM_CHANNEL_4:{/* Disable the TIM Capture/Compare 4 interrupt */__HAL_TIM_DISABLE_DMA(htim, TIM_DMA_CC4);}break;default:break;}---------------------------------------------------------------------after a complete transmission.Before there were no use of this function, because between 2 data block should be enough space (only every 50ms one data block).But with the little reconfiguration in the function there was no interference on the line and the flickering was goneThanks for your suggestions!

author: JPortici
author_rank: Super Contributor
timestamp: June 23, 2017, 10:07:46 am
content: please use the CODE tag

author: Dubbie
author_rank: Unknown
timestamp: June 23, 2017, 10:12:49 am
content: thanks a lot for updating us with the solve.