2024-03-04 02:39 AM - edited 2024-03-04 02:51 AM
I have been reading a bit about neopixels and the communication protocol they work. I have been searching for a library for the WS2812B neopixel but could not find one that would work out of the box hence I have decided to write my own neopixel driver. This would also help me further understand DMA peripheral.
It is worth mentioning that I use STM32CubeIDE as my development environment and I use STM32CubeMX to configure the peripherals.
When setting up my project I refered to the example below:
https://controllerstech.com/pwm-with-dma-in-stm32/
but I still could not get it to work properly. I suspect that the issues lies within the DMA.
1.Clock configuration
I have decided to use TIM2. According to the STM32F407 Block diagram (STM32F407 Datasheet ) , this Timer connects to AHB/APB1 hence I have set the APB1 to 48MHz to achieve the required Clock frequency for the neopixels.
2. Timer configuration:
3. DMA configuration:
4. Full source code:
/* USER CODE BEGIN Header */
/**
******************************************************************************
* @file : main.c
* @brief : Main program body
******************************************************************************
* @attention
*
* Copyright (c) 2024 STMicroelectronics.
* All rights reserved.
*
* This software is licensed under terms that can be found in the LICENSE file
* in the root directory of this software component.
* If no LICENSE file comes with this software, it is provided AS-IS.
*
******************************************************************************
*/
/* USER CODE END Header */
/* Includes ------------------------------------------------------------------*/
#include "main.h"
/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */
#ifdef __GNUC__
/* With GCC/RAISONANCE, small printf (option LD Linker->Libraries->Small printf
set to 'Yes') calls __io_putchar() */
#define PUTCHAR_PROTOTYPE int __io_putchar(int ch)
#else
#define PUTCHAR_PROTOTYPE int fputc(int ch, FILE *f)
#endif /* __GNUC__ */
/* USER CODE END Includes */
/* Private typedef -----------------------------------------------------------*/
/* USER CODE BEGIN PTD */
/* USER CODE END PTD */
/* Private define ------------------------------------------------------------*/
/* USER CODE BEGIN PD */
/* USER CODE END PD */
/* Private macro -------------------------------------------------------------*/
/* USER CODE BEGIN PM */
/* USER CODE END PM */
/* Private variables ---------------------------------------------------------*/
TIM_HandleTypeDef htim2;
DMA_HandleTypeDef hdma_tim2_ch1;
UART_HandleTypeDef huart3;
/* USER CODE BEGIN PV */
PUTCHAR_PROTOTYPE {
HAL_UART_Transmit(&huart3, (uint8_t*) &ch, 1, 0xFFFF);
return ch;
}
#define PWM_HI (67)
#define PWM_LO (33)
int datasentflag=0;
uint16_t pwmData[24];
static void WS2812_Send (uint32_t color)
{
for (int i=23; i>=0; i--)
{
if (color&(1<<i))
{
pwmData[i] = PWM_HI;
}
else pwmData[i] = PWM_LO;
}
HAL_TIM_PWM_Start_DMA(&htim2, TIM_CHANNEL_1, (uint32_t *)pwmData, 24);
while (!datasentflag){};
datasentflag = 0;
}
/* USER CODE END PV */
/* Private function prototypes -----------------------------------------------*/
void SystemClock_Config(void);
static void MX_GPIO_Init(void);
static void MX_DMA_Init(void);
static void MX_USART3_UART_Init(void);
static void MX_TIM2_Init(void);
/* USER CODE BEGIN PFP */
/* USER CODE END PFP */
/* Private user code ---------------------------------------------------------*/
/* USER CODE BEGIN 0 */
/* USER CODE END 0 */
/**
* @brief The application entry point.
* @retval int
*/
int main(void)
{
/* USER CODE BEGIN 1 */
/* USER CODE END 1 */
/* MCU Configuration--------------------------------------------------------*/
/* Reset of all peripherals, Initializes the Flash interface and the Systick. */
HAL_Init();
/* USER CODE BEGIN Init */
/* USER CODE END Init */
/* Configure the system clock */
SystemClock_Config();
/* USER CODE BEGIN SysInit */
/* USER CODE END SysInit */
/* Initialize all configured peripherals */
MX_GPIO_Init();
MX_DMA_Init();
MX_USART3_UART_Init();
MX_TIM2_Init();
/* USER CODE BEGIN 2 */
/* USER CODE END 2 */
/* Infinite loop */
/* USER CODE BEGIN WHILE */
while (1) {
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
uint32_t colour = 0xff0000; // colour code for some color
WS2812_Send(colour);
HAL_Delay(1000);
}
/* USER CODE END 3 */
}
/**
* @brief System Clock Configuration
* @retval None
*/
void SystemClock_Config(void)
{
RCC_OscInitTypeDef RCC_OscInitStruct = {0};
RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};
/** Configure the main internal regulator output voltage
*/
__HAL_RCC_PWR_CLK_ENABLE();
__HAL_PWR_VOLTAGESCALING_CONFIG(PWR_REGULATOR_VOLTAGE_SCALE1);
/** Initializes the RCC Oscillators according to the specified parameters
* in the RCC_OscInitTypeDef structure.
*/
RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSI;
RCC_OscInitStruct.HSIState = RCC_HSI_ON;
RCC_OscInitStruct.HSICalibrationValue = RCC_HSICALIBRATION_DEFAULT;
RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSI;
RCC_OscInitStruct.PLL.PLLM = 8;
RCC_OscInitStruct.PLL.PLLN = 96;
RCC_OscInitStruct.PLL.PLLP = RCC_PLLP_DIV2;
RCC_OscInitStruct.PLL.PLLQ = 4;
if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK)
{
Error_Handler();
}
/** Initializes the CPU, AHB and APB buses clocks
*/
RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK
|RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2;
RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;
RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV2;
RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV2;
RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1;
if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_1) != HAL_OK)
{
Error_Handler();
}
}
/**
* @brief TIM2 Initialization Function
* None
* @retval None
*/
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 = 100-1;
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();
}
/* USER CODE BEGIN TIM2_Init 2 */
/* USER CODE END TIM2_Init 2 */
HAL_TIM_MspPostInit(&htim2);
}
/**
* @brief USART3 Initialization Function
* None
* @retval None
*/
static void MX_USART3_UART_Init(void)
{
/* USER CODE BEGIN USART3_Init 0 */
/* USER CODE END USART3_Init 0 */
/* USER CODE BEGIN USART3_Init 1 */
/* USER CODE END USART3_Init 1 */
huart3.Instance = USART3;
huart3.Init.BaudRate = 115200;
huart3.Init.WordLength = UART_WORDLENGTH_8B;
huart3.Init.StopBits = UART_STOPBITS_1;
huart3.Init.Parity = UART_PARITY_NONE;
huart3.Init.Mode = UART_MODE_TX_RX;
huart3.Init.HwFlowCtl = UART_HWCONTROL_NONE;
huart3.Init.OverSampling = UART_OVERSAMPLING_16;
if (HAL_UART_Init(&huart3) != HAL_OK)
{
Error_Handler();
}
/* USER CODE BEGIN USART3_Init 2 */
/* USER CODE END USART3_Init 2 */
}
/**
* Enable DMA controller clock
*/
static void MX_DMA_Init(void)
{
/* DMA controller clock enable */
__HAL_RCC_DMA1_CLK_ENABLE();
/* DMA interrupt init */
/* DMA1_Stream5_IRQn interrupt configuration */
HAL_NVIC_SetPriority(DMA1_Stream5_IRQn, 0, 0);
HAL_NVIC_EnableIRQ(DMA1_Stream5_IRQn);
}
/**
* @brief GPIO Initialization Function
* None
* @retval None
*/
static void MX_GPIO_Init(void)
{
/* USER CODE BEGIN MX_GPIO_Init_1 */
/* USER CODE END MX_GPIO_Init_1 */
/* GPIO Ports Clock Enable */
__HAL_RCC_GPIOA_CLK_ENABLE();
__HAL_RCC_GPIOD_CLK_ENABLE();
/* USER CODE BEGIN MX_GPIO_Init_2 */
/* USER CODE END MX_GPIO_Init_2 */
}
/* USER CODE BEGIN 4 */
void HAL_TIM_PWM_PulseFinishedCallback(TIM_HandleTypeDef *htim) {
HAL_TIM_PWM_Stop_DMA(htim, TIM_CHANNEL_1);
datasentflag = 1;
}
/* USER CODE END 4 */
/**
* @brief This function is executed in case of error occurrence.
* @retval None
*/
void Error_Handler(void)
{
/* USER CODE BEGIN Error_Handler_Debug */
/* User can add his own implementation to report the HAL error return state */
__disable_irq();
while (1) {
}
/* USER CODE END Error_Handler_Debug */
}
#ifdef USE_FULL_ASSERT
/**
* @brief Reports the name of the source file and the source line number
* where the assert_param error has occurred.
* file: pointer to the source file name
* line: assert_param error line source number
* @retval None
*/
void assert_failed(uint8_t *file, uint32_t line)
{
/* USER CODE BEGIN 6 */
/* User can add his own implementation to report the file name and line number,
ex: printf("Wrong parameters value: file %s on line %d\r\n", file, line) */
/* USER CODE END 6 */
}
#endif /* USE_FULL_ASSERT */
I have connected a logic analyzer to the data line and I have also placed a breakpoint before the following function:
HAL_TIM_PWM_Start_DMA(&htim2, TIM_CHANNEL_1, (uint32_t *)pwmData, 24);
to inspect the pwmData.
The pwmData is as following:
pwmData[0] uint16_t 0x13
pwmData[1] uint16_t 0x13
pwmData[2] uint16_t 0x13
pwmData[3] uint16_t 0x13
pwmData[4] uint16_t 0x13
pwmData[5] uint16_t 0x13
pwmData[6] uint16_t 0x13
pwmData[7] uint16_t 0x13
pwmData[8] uint16_t 0x13
pwmData[9] uint16_t 0x13
pwmData[10] uint16_t 0x13
pwmData[11] uint16_t 0x13
pwmData[12] uint16_t 0x13
pwmData[13] uint16_t 0x13
pwmData[14] uint16_t 0x13
pwmData[15] uint16_t 0x13
pwmData[16] uint16_t 0x26
pwmData[17] uint16_t 0x26
pwmData[18] uint16_t 0x26
pwmData[19] uint16_t 0x26
pwmData[20] uint16_t 0x26
pwmData[21] uint16_t 0x26
pwmData[22] uint16_t 0x26
pwmData[23] uint16_t 0x26
However, when I look at the data line through logic analyzer it looks as following:
which does not match the pwmData at all..
I would really appreciate if someone could provide some insights on what could be wrong? I suspect the issue lies within my DMA configuration?
Other questions:
1. What is the correct Length to pass to the HAL_TIM_PWM_Start_DMA in my particular example?
Since I only want to send 24 bits ( 3 bytes ), I should call I pass 3 or 24 as length? In the example post that I initially refered the guy is using 24 but I am slightly confused.
2. What is the correct DMA data width. Currently, I have set to Word but I am not fully certain that this is correct.
Solved! Go to Solution.
2024-03-05 02:11 AM - edited 2024-03-05 02:48 AM
For example, even the official ST post about the PWM and DMA did not have this implemented:
https://community.st.com/t5/stm32-mcus/custom-signal-generation-using-pwm-and-dma/ta-p/49809
That one uses Circular DMA, i.e. the pulse train never stops. That's very different from your case.
I've told you, details matter.
And that's also the problem with Cube/HAL, too; not that it would be buggy or what, but that it's a huge bundle and it matters how exactly do you use it.
help reduce the development time greatly
You've just spent lots of time trying to fix your problem, writing this post; and I've spent lots of time understanding your problem, and previously I've spent time understanding DMA and TIM in general, and much more time understanding problems with Cube/HAL (just to be able to reply to those who use Cube/HAL, as I don't use it). You've just used your and *my* time.
No, at the end of the day, if what you've clicked in CubeMX does not work "na prvou dobrou", you'll spend *more* time than if you'd program normally.
[EDIT] Just to make things absolutely clear: the zero value at the end of array is *not* Cube/HAL specific; you'd do that when programming normally, too - unless using TIM with RCR (and pulse number fitting width of TIMx_RCR), there's no other good way to "stop" a PWM pulse train - relying on interrupts is not a good way and should be last resort with very good control over the interrupts.
But to be able to program DMA and TIM without Cube, you'd need to read the relevant portions of the manual, and from that you'd also be acutely aware of the fact, that you need to somehow stop the timer outputting PWM in a timely manner, so you wouldn't have the above problem at the first place. You do have it because you believe that Cube/HAL and CubeMX does have a good and definitive answer to your particular problem. It may or may not have, depending on what the details of the problem were; and to find out, which one is the case, is not trivial. Also, some consequences of using Cube/HAL may or may not come up later when you add other features potentially changing the context in which the Cube/HAL functions fabric works - and you'd again avoid that by having full control over the program.
Details matter, control matters.
But you do you.
[/EDIT]
JW
2024-03-04 03:13 AM
Hello @LPetr.1
It's possible that the timing of the PWM signal generated by the microcontroller is the problem, rather than the DMA configuration. You may need to adjust the timing parameters in the TIM2 initialization function to match the requirements of the WS2812 LED strip.
To answer your other questions:
The correct length to pass to the HAL_TIM_PWM_Start_DMA function would be 24, since you are sending 24 bits of data.
The correct DMA data width would be half-word (16 bits) since the data being sent is 16 bits (2 bytes) per LED.
To give better visibility on the answered topics, please click on Accept as Solution on the reply which solved your issue or answered your question.
2024-03-04 03:22 AM - edited 2024-03-04 03:23 AM
First of all, thanks for a quick reply.
At this point, I am not even interested to know why the LED is not working. I am simply confuse why the PWM waveforms does not match the pwmData in my program which it should.
To simplify, I can do a little experiment:
for (int i=0; i<8; i++)
{
pwmData[i] = 20;
}
for (int i=8; i<16; i++)
{
pwmData[i] = 40;
}
for (int i=16; i<24; i++)
{
pwmData[i] = 60;
}
HAL_TIM_PWM_Start_DMA(&htim2, TIM_CHANNEL_1, (uint32_t *)pwmData, 24);
As you can see from the code above, I have set the first 8 bits to 20% duty cycle, the next 8 bits to 40% duty cycle and then the last 8 bits to the 60% duty cycle. I am monitoring this pin using the logic analyzer and this is the result:
As you can see, the PWM does not work as supposed to. I feel like it just generates some random gibberish...
@Sarra.S wrote:
The correct DMA data width would be half-word (16 bits) since the data being sent is 16 bits (2 bytes) per LED.
How did you come up with that conclusion? For each Neopixel LED I need to send 3 bytes (not 2).
2024-03-04 03:44 AM
Update
I just want to mention that I can use PWM without the DMA and it will work without any issues. For example:
TIM2->CCR1 = 50;
HAL_TIM_PWM_Start(&htim2, TIM_CHANNEL_1);
But when I try to use the DMA, it does not work as I shown above.
2024-03-04 04:56 AM - edited 2024-03-04 04:59 AM
@Sarra.S
I think I went ahead of myself. Lets step back a little and try to understand how does DMA work. I have the most basic example:
My DMA settings:
Mode: Normal
Direction: Memory To Peripheral
Data Width: Word
My timer settings (Clocked at 84MHz)
In my code, I simply try to generate 8 waveforms with increasing duty cycle:
uint32_t pwm_data[8];
pwm_data[0] = 10;
pwm_data[1] = 20;
pwm_data[2] = 30;
pwm_data[3] = 40;
pwm_data[4] = 50;
pwm_data[5] = 60;
pwm_data[6] = 70;
pwm_data[7] = 80;
HAL_TIM_PWM_Start_DMA(&htim2, TIM_CHANNEL_1, (uint32_t *)pwm_data, 8);
The result:
As you can see from the waveforms above, it appears to be sending 11 pulses with increasing duty cycles, then it stays HIGH for a long time before finally going DOWN.
1. Why does it send more pulses than I want to? (I only want to send 8 pulses)
Data width in the above experiment was set to Word. Does it have to match the pwm_data data size? What if I create a variable instead of uint32_t pwm_data[8] -> uint16_t pwm_data[8];
In that case, Word data width will no longer be correct or what?
2024-03-04 05:36 AM - edited 2024-03-04 05:37 AM
After the "last" pulse, it will continue to send that pulse until something changes the value. I suspect your pulse complete callback, or similar, is eventually changing things. In this example, add another value such as 0 or 100 (depending on if you want it low or high) to the data set, disable callbacks, and try again.
uint32_t pwm_data[9];
pwm_data[0] = 10;
pwm_data[1] = 20;
pwm_data[2] = 30;
pwm_data[3] = 40;
pwm_data[4] = 50;
pwm_data[5] = 60;
pwm_data[6] = 70;
pwm_data[7] = 80;
pwm_data[8] = 0;
__disable_irq();
HAL_TIM_PWM_Start_DMA(&htim2, TIM_CHANNEL_1, (uint32_t *)pwm_data, 9);
while (1);
If you want to send uint32_t values, the DMA has to be set to word width. If you want to send uint16_t, it should be half word.
2024-03-04 09:01 AM
> If you want to send uint32_t values, the DMA has to be set to word width. If you want to send uint16_t, it should be half word.
It's not just matter of "you want". TIM2 is 32-bit, its registers are word-accessible. If you write 16-bit values to its registers (regardless of whether via DMA or processor, except that it would require extra work with casting if you'd do it by processor), the AHB/APB bridge doubles the halfword into both halves of the word, resulting in surprising behaviour.
> After the "last" pulse, it will continue to send that pulse until something changes the value. I suspect your pulse complete callback, or similar, is eventually changing things.
Callback plus whatever Cube/HAL threw in.
[rant mode on] In the last month, I've seen here 3 similar attempts to throw Cube/HAL at the addressable LEDs, with the same results - lack of control. In one of the attempts the OP drew the clear conclusion that DMA in 'F1 is broken. Details matter, so accept that you have to learn and understand everything you didn't want to learn at the first place, and spend time and effort you've tried to avoid when started clicking in Cube. If you want to stick to Cube/HAL, accept, that it is now part of your program and it's your responsibility what's in there, even if you did not write it. Cube/HAL is a tool to satisfy "typical" requirements by clicking, and to sell chips to managers who want to hear "quickly and cheaply". This may work out, or not; in the latter case you're worse off than if you'd never started with Cubes, because they are psychologically almost impossible to unlearn. [rant mode off]
Add a zero length pulse at the end of your pulsetrain, that will solve the problem with extra pulses. Whatever comes after it, and whatever defect may be at the beginning of the pulse train, may need to be doctored through understanding of how the timer works and what's written in Cube.
JW
2024-03-04 09:51 PM
First of all, thanks for the replies, I really appreciate all.
By using the zero length pulse as has been suggested it seems that the issue goes away.
uint32_t pwm_data[9];
pwm_data[0] = 10;
pwm_data[1] = 20;
pwm_data[2] = 30;
pwm_data[3] = 40;
pwm_data[4] = 50;
pwm_data[5] = 60;
pwm_data[6] = 70;
pwm_data[7] = 80;
pwm_data[8] = 0;
__disable_irq();
HAL_TIM_PWM_Start_DMA(&htim2, TIM_CHANNEL_1, (uint32_t *)pwm_data, 9);
Is that the usual solution when it comes to sending PWM via DMA? I have looked at multiple examples online but none of them seemed to be using the zero length pulse at the end to avoid having the issues that I do with the additional pulses. For example, even the official ST post about the PWM and DMA did not have this implemented:
https://community.st.com/t5/stm32-mcus/custom-signal-generation-using-pwm-and-dma/ta-p/49809
Could there be any other reasons why it would not work without the zero length as it seems to be working for others or you are suggesting that this fault is due to CubeMX initialization and HAL drivers that I used? In all the projects that I have previously looked at (as well as the official ST post about PWM/DMA I have shown above), all use CubeMX/HAL for their implementation and they do not seem to have such problems. Perhaps this is somehow related to the STM32F407 MCU that I am using?
Also, many seem to be quite frustrated and angry about the CubeMX/HAL. I dont really understand why is that the case. Perhaps I do not have enough experience to truly talk about these tools but they seem quite good and help reduce the development time greatly. I guess it would make sense not using HAL/MX if you want to utilize every bit of optimization possible, but for simple projects such as reading a couple of ADC's, generating some PWM pulses, I feel like CubeMX/HAL is the way to go. Perhaps I could try not using MX/HAL for one project to see how it goes maybe then my opinion will change.
CubeMX received a ton of updates through the years and I would think that at this point it should be quite reliable?
2024-03-05 02:11 AM - edited 2024-03-05 02:48 AM
For example, even the official ST post about the PWM and DMA did not have this implemented:
https://community.st.com/t5/stm32-mcus/custom-signal-generation-using-pwm-and-dma/ta-p/49809
That one uses Circular DMA, i.e. the pulse train never stops. That's very different from your case.
I've told you, details matter.
And that's also the problem with Cube/HAL, too; not that it would be buggy or what, but that it's a huge bundle and it matters how exactly do you use it.
help reduce the development time greatly
You've just spent lots of time trying to fix your problem, writing this post; and I've spent lots of time understanding your problem, and previously I've spent time understanding DMA and TIM in general, and much more time understanding problems with Cube/HAL (just to be able to reply to those who use Cube/HAL, as I don't use it). You've just used your and *my* time.
No, at the end of the day, if what you've clicked in CubeMX does not work "na prvou dobrou", you'll spend *more* time than if you'd program normally.
[EDIT] Just to make things absolutely clear: the zero value at the end of array is *not* Cube/HAL specific; you'd do that when programming normally, too - unless using TIM with RCR (and pulse number fitting width of TIMx_RCR), there's no other good way to "stop" a PWM pulse train - relying on interrupts is not a good way and should be last resort with very good control over the interrupts.
But to be able to program DMA and TIM without Cube, you'd need to read the relevant portions of the manual, and from that you'd also be acutely aware of the fact, that you need to somehow stop the timer outputting PWM in a timely manner, so you wouldn't have the above problem at the first place. You do have it because you believe that Cube/HAL and CubeMX does have a good and definitive answer to your particular problem. It may or may not have, depending on what the details of the problem were; and to find out, which one is the case, is not trivial. Also, some consequences of using Cube/HAL may or may not come up later when you add other features potentially changing the context in which the Cube/HAL functions fabric works - and you'd again avoid that by having full control over the program.
Details matter, control matters.
But you do you.
[/EDIT]
JW
2024-03-05 05:56 AM
I dont know that not using HAL would have done anything here. The issue was that the functionality of what happens after DMA is done was not understood. Using direct register access would have lead to the same result.