2021-01-05 01:23 AM
Hello,
as many before I am trying to control some SK6812 adressable LEDs with a STM32 MCU. I have successfully done so on a custom board using the STM32L433CCT and this neat guide:
https://www.thevfdcollective.com/blog/stm32-and-sk6812-rgbw-led
and this gihub repository: https://github.com/hey-frnk/STM32_HAL_NeoPixel
I follwed the tutorial step by step using CubeMX and SW4STM32.
Now however I have to switch to a STM32F469l-DISCO board and I thought well the same code should work right? No! Looking at the signal using a scope, I saw that it was nowhere near the needed 800kHz. So after looking around in manuals and posts I figured out, that some changes to the DMA setup have to be made. See this post:
https://community.st.com/s/question/0D50X00009XkeDcSAJ/stm32f4-dma-and-varying-duty-cycle
So and here is my problem I dont really understand what exactly I have to change since they are setting up their DMA manually in the post above, while I used CubeMX to set it up again without FIFO and couldnt find such DMA_Init code in my main.c file. With FIFO.
Edit: So after taking a break and thinking about the problem a bit, I think that my led_render, PulseHalfFinishedCallback and PulseFinishedCallback functions need to be adjusted to fit the new DMA Sorry for my misleading first question (if it really wasnt the problem :^) ). Code from the git above inbound:
// LED parameters
#define NUM_BPP (4) // SK6812
#define NUM_PIXELS (8) //number of LEDs controlled for tim1 -> adjust for application needed
#define NUM_BYTES (NUM_BPP * NUM_PIXELS)
// LED color buffer
uint8_t rgb_arr[NUM_BYTES] = {0};//Tim1
// LED write buffer
#define WR_BUF_LEN (NUM_BPP * 8 * 2)//Tim1
uint8_t wr_buf[WR_BUF_LEN] = {0};
uint_fast8_t wr_buf_p = 0;
void led_render() {
if(wr_buf_p != 0 || hdma_tim1_ch1.State != HAL_DMA_STATE_READY) {
// Ongoing transfer, cancel!
for(uint8_t i = 0; i < WR_BUF_LEN; ++i) wr_buf[i] = 0;
wr_buf_p = 0;
HAL_TIM_PWM_Stop_DMA(&htim1, TIM_CHANNEL_1);
return;
}
// Ooh boi the first data buffer half (and the second!)
for(uint_fast8_t i = 0; i < 8; ++i) {
wr_buf[i ] = PWM_LO << (((rgb_arr[0] << i) & 0x80) > 0);
wr_buf[i + 8] = PWM_LO << (((rgb_arr[1] << i) & 0x80) > 0);
wr_buf[i + 16] = PWM_LO << (((rgb_arr[2] << i) & 0x80) > 0);
wr_buf[i + 24] = PWM_LO << (((rgb_arr[3] << i) & 0x80) > 0);
wr_buf[i + 32] = PWM_LO << (((rgb_arr[4] << i) & 0x80) > 0);
wr_buf[i + 40] = PWM_LO << (((rgb_arr[5] << i) & 0x80) > 0);
wr_buf[i + 48] = PWM_LO << (((rgb_arr[6] << i) & 0x80) > 0);
wr_buf[i + 56] = PWM_LO << (((rgb_arr[7] << i) & 0x80) > 0);
}
HAL_TIM_PWM_Start_DMA(&htim1, TIM_CHANNEL_1, (uint32_t *)wr_buf, WR_BUF_LEN);
wr_buf_p = 2; // Since we're ready for the next buffer
}
void HAL_TIM_PWM_PulseFinishedHalfCpltCallback(TIM_HandleTypeDef *htim) {
// DMA buffer set from LED(wr_buf_p) to LED(wr_buf_p + 1)
if(wr_buf_p < NUM_PIXELS) {
// We're in. Fill the even buffer
for(uint_fast8_t i = 0; i < 8; ++i) {
wr_buf[i ] = PWM_LO << (((rgb_arr[4 * wr_buf_p ] << i) & 0x80) > 0);
wr_buf[i + 8] = PWM_LO << (((rgb_arr[4 * wr_buf_p + 1] << i) & 0x80) > 0);
wr_buf[i + 16] = PWM_LO << (((rgb_arr[4 * wr_buf_p + 2] << i) & 0x80) > 0);
wr_buf[i + 24] = PWM_LO << (((rgb_arr[4 * wr_buf_p + 3] << i) & 0x80) > 0);
}
wr_buf_p++;
} else if (wr_buf_p < NUM_PIXELS + 2) {
// Last two transfers are resets. SK6812: 64 * 1.25 us = 80 us == good enough reset
// First half reset zero fill
for(uint8_t i = 0; i < WR_BUF_LEN / 2; ++i) wr_buf[i] = 0;
wr_buf_p++;
}
}
void HAL_TIM_PWM_PulseFinishedCallback(TIM_HandleTypeDef *htim) {
// DMA buffer set from LED(wr_buf_p) to LED(wr_buf_p + 1)
if(wr_buf_p < NUM_PIXELS) {
// We're in. Fill the odd buffer
for(uint_fast8_t i = 0; i < 8; ++i) {
wr_buf[i + 32] = PWM_LO << (((rgb_arr[4 * wr_buf_p ] << i) & 0x80) > 0);
wr_buf[i + 40] = PWM_LO << (((rgb_arr[4 * wr_buf_p + 1] << i) & 0x80) > 0);
wr_buf[i + 48] = PWM_LO << (((rgb_arr[4 * wr_buf_p + 2] << i) & 0x80) > 0);
wr_buf[i + 56] = PWM_LO << (((rgb_arr[4 * wr_buf_p + 3] << i) & 0x80) > 0);
}
wr_buf_p++;
} else if (wr_buf_p < NUM_PIXELS + 2) {
// Second half reset zero fill
for(uint8_t i = WR_BUF_LEN / 2; i < WR_BUF_LEN; ++i) wr_buf[i] = 0;
++wr_buf_p;
} else {
// We're done. Lean back and until next time!
wr_buf_p = 0;
HAL_TIM_PWM_Stop_DMA(&htim2, TIM_CHANNEL_1);
}
}
Any help on what changes need to be made , whether with CubeMX or via coding, are much appreciated!
Solved! Go to Solution.
2021-01-07 04:17 AM
See here for my other question with solution: https://community.st.com/s/feed/0D53W00000ToyBISAZ
2021-01-05 01:30 AM
Hi @Fishmaster257
Can you please share you ioc file to check the problem of DMA_Init generation function in main ?
Thanks, Khouloud
2021-01-05 01:32 AM
The base timing is going to depend on the clocks to the bus. The Prescaler and Period of the TIM will need to be changed to hit the desired frequency. In some cases the CPU frequency will need changing if integer dividers are not sufficient.
The HAL or LL should provide the means to drive a TIMs CCRx registers. The DMA TIM relationship will be in the Reference Manual. The older SPL might have been easier.
2021-01-05 02:12 AM
Thanks for your fast answers. @Khouloud ZEMMELI I pinned the .ioc file to this comment. As far as my understanding goes, my DMA_Init only sets up the interrupts needed fot the DMA itself.
Concerning the clocks on the bus, I set up the Sysclock to be 180MHz and through the APB1/ABP2 prescalers and multipliers I set up the APB1/APB2 timer clocks to be 90MHz. I didnt mention it earlier, thought it didn't matter. I want to use two DMA channels and Timers for a parallel control of 2 LED-strips.
My timer periods are 111 (112) each, resulting in ~803,6 kHz (1,244 µs) so well within the range of the LEDs. My low duty cycle being 28 (25% and logic 0) and my high duty cycle being 56 (50% and logic 1), which are needed for the logic ones and zeros the LEDs understand.
Thanks again.
2021-01-07 04:17 AM
See here for my other question with solution: https://community.st.com/s/feed/0D53W00000ToyBISAZ