2021-01-07 02:14 AM
Hello,
I am having a problem with my STM32F369l-DISCO board, as the title says it has to do with the DMA and PWM output.
So I need a 800kHz PWM signal for some SK6812 LEDs, I already did achieve this with an STM32L433CCT on a custom board using this guide: https://www.thevfdcollective.com/blog/stm32-and-sk6812-rgbw-led
I set up my Project with CubeMX, the DMA setup there looked like this:
with this I got my desired DMA/PWM-frequency when using:
HAL_TIM_PWM_Start_DMA(&htim2, TIM_CHANNEL_1,"Data","Lengt");
Now as I said I switched to a STM32F469l-DISCO board.
There the setup looked like this:
My clocks like this:
with a Timer period of 112-1 resulting in ~800kHz
I also tried it without FIFO, but then I am not able to select memorysize BYTE. It doesn´t really matter, it made almost no difference. To get to my Problem, when trying to output my PWM signal with this code from the guide above:
#define PWM_HI (56) //50%DC
#define PWM_LO (28) //25%DC
// LED parameters
#define NUM_BPP (4) // SK6812
#define NUM_PIXELS2 (8) ///number of LEDs controlled by tim2
#define NUM_BYTES (NUM_BPP * NUM_PIXELS2)
#define WR_BUF_LEN2 (NUM_BPP * 8 * 2)//Tim2
uint8_t wr_buf2[WR_BUF_LEN2] = {0};
uint_fast8_t wr_buf_p2 = 0;
uint8_t rgb_arr2[NUM_BYTES2]={0};///1 BYTE per colour, max value 255. 4 BYTE= 1 LED
void led_render2() {
if(wr_buf_p2 != 0 || hdma_tim2_ch1.State != HAL_DMA_STATE_READY) {
// Ongoing transfer, cancel!
for(uint8_t i = 0; i < WR_BUF_LEN2; ++i) wr_buf2[i] = 0;
wr_buf_p2 = 0;
HAL_TIM_PWM_Stop_DMA(&htim2, TIM_CHANNEL_1);
return;
}
// Ooh boi the first data buffer half (and the second!)
for(uint_fast8_t i = 0; i < 8; ++i) {
wr_buf2[i ] = PWM_LO << (((rgb_arr2[0] << i) & 0x80) > 0);
wr_buf2[i + 8] = PWM_LO << (((rgb_arr2[1] << i) & 0x80) > 0);
wr_buf2[i + 16] = PWM_LO << (((rgb_arr2[2] << i) & 0x80) > 0);
wr_buf2[i + 24] = PWM_LO << (((rgb_arr2[3] << i) & 0x80) > 0);
wr_buf2[i + 32] = PWM_LO << (((rgb_arr2[4] << i) & 0x80) > 0);
wr_buf2[i + 40] = PWM_LO << (((rgb_arr2[5] << i) & 0x80) > 0);
wr_buf2[i + 48] = PWM_LO << (((rgb_arr2[6] << i) & 0x80) > 0);
wr_buf2[i + 56] = PWM_LO << (((rgb_arr2[7] << i) & 0x80) > 0);
}
HAL_TIM_PWM_Start_DMA(&htim2, TIM_CHANNEL_1, (uint32_t *)wr_buf2, WR_BUF_LEN2);
wr_buf_p2 = 2; // Since we're ready for the next buffer
}
I get this signal on an oscilloscope:
but when using this code to directly set dutycycle without DMA it looks different:
__HAL_TIM_SET_COMPARE(&htim2,TIM_CHANNEL_1,56);
HAL_TIM_PWM_Start(&htim2,TIM_CHANNEL_1);
as you can see this results in the desired output signal. I am pretty sure this has something to either with my DMA setup or my approach on feeding the data to the DMA, yet I am not sure what the problem exactly ist. I tried it with FIFO an almost every burst/threshold combination there is yet it didn´t work. I also looked at the datasheet for the DMA without success though.
This is the code, CubeMX generates for my Timer (should be correct):
static void MX_TIM2_Init(void)
{
/* USER CODE BEGIN TIM2_Init 0 */
/* USER CODE END TIM2_Init 0 */
TIM_ClockConfigTypeDef sClockSourceConfig = {0};
TIM_MasterConfigTypeDef sMasterConfig = {0};
TIM_OC_InitTypeDef sConfigOC = {0};
/* USER CODE BEGIN TIM2_Init 1 */
/* USER CODE END TIM2_Init 1 */
htim2.Instance = TIM2;
htim2.Init.Prescaler = 0;
htim2.Init.CounterMode = TIM_COUNTERMODE_UP;
htim2.Init.Period = 111;
htim2.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
htim2.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_DISABLE;
if (HAL_TIM_Base_Init(&htim2) != HAL_OK)
{
Error_Handler();
}
sClockSourceConfig.ClockSource = TIM_CLOCKSOURCE_INTERNAL;
if (HAL_TIM_ConfigClockSource(&htim2, &sClockSourceConfig) != HAL_OK)
{
Error_Handler();
}
if (HAL_TIM_PWM_Init(&htim2) != HAL_OK)
{
Error_Handler();
}
sMasterConfig.MasterOutputTrigger = TIM_TRGO_RESET;
sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE;
if (HAL_TIMEx_MasterConfigSynchronization(&htim2, &sMasterConfig) != HAL_OK)
{
Error_Handler();
}
sConfigOC.OCMode = TIM_OCMODE_PWM1;
sConfigOC.Pulse = 0;
sConfigOC.OCPolarity = TIM_OCPOLARITY_HIGH;
sConfigOC.OCFastMode = TIM_OCFAST_DISABLE;
if (HAL_TIM_PWM_ConfigChannel(&htim2, &sConfigOC, TIM_CHANNEL_1) != HAL_OK)
{
Error_Handler();
}
And this the init code for my DMA, which is probably not 100% fitting:
void HAL_TIM_Base_MspInit(TIM_HandleTypeDef* htim_base)
{
if(htim_base->Instance==TIM1)
{
//the same code but for TIM1
}
else if(htim_base->Instance==TIM2)
{
/* USER CODE BEGIN TIM2_MspInit 0 */
/* USER CODE END TIM2_MspInit 0 */
/* Peripheral clock enable */
__HAL_RCC_TIM2_CLK_ENABLE();
/* TIM2 DMA Init */
/* TIM2_CH1 Init */
hdma_tim2_ch1.Instance = DMA1_Stream5;
hdma_tim2_ch1.Init.Channel = DMA_CHANNEL_3;
hdma_tim2_ch1.Init.Direction = DMA_MEMORY_TO_PERIPH;
hdma_tim2_ch1.Init.PeriphInc = DMA_PINC_DISABLE;
hdma_tim2_ch1.Init.MemInc = DMA_MINC_ENABLE;
hdma_tim2_ch1.Init.PeriphDataAlignment = DMA_PDATAALIGN_WORD;
hdma_tim2_ch1.Init.MemDataAlignment = DMA_MDATAALIGN_WORD; // I tried manually setting this to Byte, without any change to the PWM signal though
hdma_tim2_ch1.Init.Mode = DMA_CIRCULAR;
hdma_tim2_ch1.Init.Priority = DMA_PRIORITY_LOW;
hdma_tim2_ch1.Init.FIFOMode = DMA_FIFOMODE_DISABLE;
// hdma_tim2_ch1.Init.FIFOMode = DMA_FIFOMODE_ENABLE; //with FIFO
// hdma_tim2_ch1.Init.FIFOThreshold = DMA_FIFO_THRESHOLD_FULL;
//hdma_tim2_ch1.Init.MemBurst = DMA_MBURST_SINGLE;
//hdma_tim2_ch1.Init.PeriphBurst = DMA_PBURST_SINGLE;
if (HAL_DMA_Init(&hdma_tim2_ch1) != HAL_OK)
{
Error_Handler();
}
__HAL_LINKDMA(htim_base,hdma[TIM_DMA_ID_CC1],hdma_tim2_ch1);
/* USER CODE BEGIN TIM2_MspInit 1 */
/* USER CODE END TIM2_MspInit 1 */
}
}
I hope I didn´t forget something important. Any hint on what I am doing wrong will help greatly, as I don´t know what else to try.
Solved! Go to Solution.
2021-01-07 03:21 AM
There are several differences between the single-port DMA in 'L4 and dual-port DMA in 'F4, but what affects you here is, that the single-port DMA, when set to byte on read side and word to write side, it zero-extends (pads) the byte into word; whereas the dual-port DMA won't (in particular, it will read 4 bytes on the read side and assemble them into a single word on the write side, if the FIFO is used; it will simply ignore the memory-side data setting if FIFO is not used, using the peripheral-side setting for both sides).
So, you have to do that "manually", by declaring the source array as words
uint32_t wr_buf2[WR_BUF_LEN2] = {0};
I don't use Cube/CubeMX, so I might've overlooked something else, too.
JW
2021-01-07 03:21 AM
There are several differences between the single-port DMA in 'L4 and dual-port DMA in 'F4, but what affects you here is, that the single-port DMA, when set to byte on read side and word to write side, it zero-extends (pads) the byte into word; whereas the dual-port DMA won't (in particular, it will read 4 bytes on the read side and assemble them into a single word on the write side, if the FIFO is used; it will simply ignore the memory-side data setting if FIFO is not used, using the peripheral-side setting for both sides).
So, you have to do that "manually", by declaring the source array as words
uint32_t wr_buf2[WR_BUF_LEN2] = {0};
I don't use Cube/CubeMX, so I might've overlooked something else, too.
JW
2021-01-07 04:16 AM
You dear sir are simply awesome!!!
That's it the whole deal, now it works again like a charm.
Have a great day !! Thank you so much I have been struggling with this DMA for four days and now it finally works :^)