I am trying to make my own driver for WS2812 LEDs with a timer generated PWM and a circular DMA buffer for conserving memory. I managed to get the right timings, however, looking at the signal with a logic analyzer, I notice that two main things go wrong:
Whether this is actually related to interrupts being constantly called I am not entirely sure. I am guessing this is the issue because not the full amount of bits is sent and I also tried to toggle a GPIO pin on either one of the interrupts and the result was that the pin always stayed off, possibly indicating that the interrupt is continuously called and so the pin does not have enough time to switch on. I am not sure if there are any other ways to test when and in what time intervals the interrupts occur.
If this is indeed the case of continuously called interrupts, what could be the issue? As far as I can tell I am using appropriate callback functions and my initializations are in order. However, the weird part that I found was that whenever I comment out the DMA PWM stop function, it starts sending out bits correctly, that is, right amount and right order. If the interrupts were called continuosly, I don't think this would work, because the DMA buffer would keep getting changed by the interrupt routines, which should cause erroneous output. I would be thankful for any tips or advice! Here's the code:
main.c
#include "main.h"
/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */
#include "ws2812.h"
/* 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;
/* USER CODE BEGIN PV */
uint8_t fbuffer[NUM_LEDS*3];
/* 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_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_TIM2_Init();
/* USER CODE BEGIN 2 */
// if(sbyte & (1 << 7)) lh = 65;
// else lh = 25;
fbuffer[0] = 255;
fbuffer[1] = 0;
fbuffer[2] = 0;
fbuffer[3] = 0;
fbuffer[4] = 0;
fbuffer[5] = 0;
fbuffer[6] = 0;
fbuffer[7] = 255;
fbuffer[8] = 0;
/* USER CODE END 2 */
/* Infinite loop */
/* USER CODE BEGIN WHILE */
while (1)
{
ws2812_show(fbuffer);
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
}
/* USER CODE END 3 */
}
/**
* @brief System Clock Configuration
* @retval None
*/
void SystemClock_Config(void)
{
RCC_OscInitTypeDef RCC_OscInitStruct = {0};
RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};
/** Initializes the RCC Oscillators according to the specified parameters
* in the RCC_OscInitTypeDef structure.
*/
RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE;
RCC_OscInitStruct.HSEState = RCC_HSE_ON;
RCC_OscInitStruct.HSEPredivValue = RCC_HSE_PREDIV_DIV1;
RCC_OscInitStruct.HSIState = RCC_HSI_ON;
RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE;
RCC_OscInitStruct.PLL.PLLMUL = RCC_PLL_MUL9;
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_DIV1;
RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV2;
RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1;
if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_2) != HAL_OK)
{
Error_Handler();
}
}
/**
* @brief TIM2 Initialization Function
* @param 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 = 89;
htim2.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
htim2.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_ENABLE;
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);
}
/**
* Enable DMA controller clock
*/
static void MX_DMA_Init(void)
{
/* DMA controller clock enable */
__HAL_RCC_DMA1_CLK_ENABLE();
/* DMA interrupt init */
/* DMA1_Channel5_IRQn interrupt configuration */
HAL_NVIC_SetPriority(DMA1_Channel5_IRQn, 0, 0);
HAL_NVIC_EnableIRQ(DMA1_Channel5_IRQn);
}
/**
* @brief GPIO Initialization Function
* @param None
* @retval None
*/
static void MX_GPIO_Init(void)
{
GPIO_InitTypeDef GPIO_InitStruct = {0};
/* GPIO Ports Clock Enable */
__HAL_RCC_GPIOD_CLK_ENABLE();
__HAL_RCC_GPIOA_CLK_ENABLE();
/*Configure GPIO pin Output Level */
HAL_GPIO_WritePin(GPIOA, GPIO_PIN_0, GPIO_PIN_RESET);
/*Configure GPIO pin : PA0 */
GPIO_InitStruct.Pin = GPIO_PIN_0;
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
GPIO_InitStruct.Pull = GPIO_NOPULL;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
}
ws2812.c
#include "main.h"
#include "ws2812.h"
extern TIM_HandleTypeDef htim2;
extern DMA_HandleTypeDef hdma_tim2_ch1;
extern uint8_t fbuffer[NUM_LEDS*3];
uint16_t indby = 3;
uint32_t bcolors = 0;
uint8_t circbuffer[24] = {0};
uint8_t rd_flag = 0;
uint8_t in_flag = 0;
// Interrupt for when half of the DMA buffer is reached.
void HAL_TIM_PWM_PulseFinishedHalfCpltCallback(TIM_HandleTypeDef *htim)
{
if (htim->Instance == htim2.Instance) {
if(indby < NUM_LEDS*3) {
// Fill the first half of the DMA buffer
bcolors = (fbuffer[indby+1] << 16) | (fbuffer[indby] << 8) | fbuffer[indby+2];
for(uint8_t i = 23; i > 11; i--) {
if(bcolors & (1<<i)) circbuffer[23-i] = HT;
else circbuffer[23-i] = LT;
}
}
}
}
// Interrupt for when all of the DMA buffer is passed.
void HAL_TIM_PWM_PulseFinishedCallback(TIM_HandleTypeDef *htim)
{
if (htim->Instance == htim2.Instance) {
if(indby < NUM_LEDS*3) {
// Set the latter half of the DMA buffer
for(uint8_t i = 11; i > 0; i--) {
if(bcolors & (1<<i)) circbuffer[23-i] = HT;
else circbuffer[23-i] = LT;
}
if(bcolors & (1<<0)) circbuffer[23] = HT;
else circbuffer[23] = LT;
indby += 3;
//HAL_GPIO_TogglePin(GPIOA, GPIO_PIN_0);
}
// Stop PWM if last LED bit reached.
else {
HAL_TIM_PWM_Stop_DMA(&htim2, TIM_CHANNEL_1);
rd_flag = 0;
}
}
}
uint8_t ws2812_show(uint8_t fbuffer[])
{
// State machine like switch to make sure the functions runs properly on
// consecutive calls
switch(0 ^ in_flag ^ (rd_flag<<1)) {
// Initialization state
case 0:
in_flag = 1;
rd_flag = 1;
bcolors = (fbuffer[1] << 16) | (fbuffer[0] << 8) | fbuffer[2];
for(uint8_t i = 23; i > 0; i--) {
if(bcolors & (1<<i)) circbuffer[23-i] = HT;
else circbuffer[23-i] = LT;
}
if(bcolors & (1<<0)) circbuffer[23] = HT;
else circbuffer[23] = LT;
HAL_TIM_PWM_Start_DMA(&htim2, TIM_CHANNEL_1, (uint32_t *)circbuffer, 24);
return 0;
break;
// Running state
case 3:
return 0;
break;
// Finished state
case 1:
in_flag = 0;
return 1;
break;
// Something is up state
case 2:
rd_flag = 0;
return 0;
break;
default:
return 0;
}
}
stm32f1xx_hal_msp.c
#include "main.h"
/* USER CODE BEGIN Includes */
/* USER CODE END Includes */
extern DMA_HandleTypeDef hdma_tim2_ch1;
/* Private typedef -----------------------------------------------------------*/
/* USER CODE BEGIN TD */
/* USER CODE END TD */
/* Private define ------------------------------------------------------------*/
/* USER CODE BEGIN Define */
/* USER CODE END Define */
/* Private macro -------------------------------------------------------------*/
/* USER CODE BEGIN Macro */
/* USER CODE END Macro */
/* Private variables ---------------------------------------------------------*/
/* USER CODE BEGIN PV */
/* USER CODE END PV */
/* Private function prototypes -----------------------------------------------*/
/* USER CODE BEGIN PFP */
/* USER CODE END PFP */
/* External functions --------------------------------------------------------*/
/* USER CODE BEGIN ExternalFunctions */
/* USER CODE END ExternalFunctions */
/* USER CODE BEGIN 0 */
/* USER CODE END 0 */
void HAL_TIM_MspPostInit(TIM_HandleTypeDef *htim);
/**
* Initializes the Global MSP.
*/
void HAL_MspInit(void)
{
/* USER CODE BEGIN MspInit 0 */
/* USER CODE END MspInit 0 */
__HAL_RCC_AFIO_CLK_ENABLE();
__HAL_RCC_PWR_CLK_ENABLE();
/* System interrupt init*/
/** NOJTAG: JTAG-DP Disabled and SW-DP Enabled
*/
__HAL_AFIO_REMAP_SWJ_NOJTAG();
/* USER CODE BEGIN MspInit 1 */
/* USER CODE END MspInit 1 */
}
/**
* @brief TIM_Base MSP Initialization
* This function configures the hardware resources used in this example
* @param htim_base: TIM_Base handle pointer
* @retval None
*/
void HAL_TIM_Base_MspInit(TIM_HandleTypeDef* htim_base)
{
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_Channel5;
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_HALFWORD;
hdma_tim2_ch1.Init.MemDataAlignment = DMA_MDATAALIGN_BYTE;
hdma_tim2_ch1.Init.Mode = DMA_CIRCULAR;
hdma_tim2_ch1.Init.Priority = DMA_PRIORITY_LOW;
if (HAL_DMA_Init(&hdma_tim2_ch1) != HAL_OK)
{
Error_Handler();
}
__HAL_LINKDMA(htim_base,hdma[TIM_DMA_ID_CC1],hdma_tim2_ch1);
/* TIM2 interrupt Init */
HAL_NVIC_SetPriority(TIM2_IRQn, 0, 0);
HAL_NVIC_EnableIRQ(TIM2_IRQn);
/* USER CODE BEGIN TIM2_MspInit 1 */
/* USER CODE END TIM2_MspInit 1 */
}
}
void HAL_TIM_MspPostInit(TIM_HandleTypeDef* htim)
{
GPIO_InitTypeDef GPIO_InitStruct = {0};
if(htim->Instance==TIM2)
{
/* USER CODE BEGIN TIM2_MspPostInit 0 */
/* USER CODE END TIM2_MspPostInit 0 */
__HAL_RCC_GPIOA_CLK_ENABLE();
/**TIM2 GPIO Configuration
PA15 ------> TIM2_CH1
*/
GPIO_InitStruct.Pin = GPIO_PIN_15;
GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
__HAL_AFIO_REMAP_TIM2_PARTIAL_1();
/* USER CODE BEGIN TIM2_MspPostInit 1 */
/* USER CODE END TIM2_MspPostInit 1 */
}
}
/**
* @brief TIM_Base MSP De-Initialization
* This function freeze the hardware resources used in this example
* @param htim_base: TIM_Base handle pointer
* @retval None
*/
void HAL_TIM_Base_MspDeInit(TIM_HandleTypeDef* htim_base)
{
if(htim_base->Instance==TIM2)
{
/* USER CODE BEGIN TIM2_MspDeInit 0 */
/* USER CODE END TIM2_MspDeInit 0 */
/* Peripheral clock disable */
__HAL_RCC_TIM2_CLK_DISABLE();
/* TIM2 DMA DeInit */
HAL_DMA_DeInit(htim_base->hdma[TIM_DMA_ID_CC1]);
/* TIM2 interrupt DeInit */
HAL_NVIC_DisableIRQ(TIM2_IRQn);
/* USER CODE BEGIN TIM2_MspDeInit 1 */
/* USER CODE END TIM2_MspDeInit 1 */
}
}
If need be, I can later provide the logic analyser waveforms.
Literally forgot to simply reset the byte counting variable indby
. Take this as a lesson to very carefully check your code!