cancel
Showing results for 
Search instead for 
Did you mean: 

RGB LED Driver with tight timing requirements (STM32C011J4M6 and WS2812C)

AdamGulyas
Associate II

I'm trying to talk to a string of WS2812C LEDs with an STM32C011J4M6. The timing requirements are:

AdamGulyas_0-1759602462479.png

Here's the schematic. I populated R3 and removed R1:

AdamGulyas_1-1759602566386.png

I'm hoping for the simplest hardware implementation, so I'm using the HSI 48MHz clock. This means that each clock cycle is 20.8ns, which means that, for a 0, the pin can only be high for at most 18 clock cycles. With that in mind, I tried to write the most efficient driver that also takes the same time for each bit.

My problem is that, when I try to output a string of 1s, the timings vary despite the code being the same for each bit:

AdamGulyas_2-1759603556372.png

I get 4 short pulses (840ns, good) followed by 4 longer pulses (1670ns, too long). Somehow, ~45 extra clock cycles are embedded in the long pulses, which doesn't make any sense since the code is the same for each bit.

Is the microcontroller doing background tasks or something? Why is the timing different in such a predictable way?

Here's my main.c:

#include "main.h"

#define NUM_COLOURS 3
#define LED_COUNT   9
#define FRAME_SIZE NUM_COLOURS*LED_COUNT	// 27

uint8_t LED_Frame[FRAME_SIZE];

void SystemClock_Config(void);
static void MX_GPIO_Init(void);

void Output0(void){
	GPIOA->BSRR = GPIO_BSRR_BS8;
	__NOP(); __NOP(); __NOP(); __NOP(); __NOP();

	GPIOA->BSRR = GPIO_BSRR_BR8;
	__NOP(); __NOP(); __NOP(); __NOP(); __NOP();
}

void Output1(void){
	GPIOA->BSRR = GPIO_BSRR_BS8;
	__NOP(); __NOP(); __NOP(); __NOP(); __NOP();

	GPIOA->BSRR = GPIO_BSRR_BR8;
	__NOP();
}

// Stores each bit of the LED_Frame into its own uint8_t value
// so it can be accessed without bit shifting (extra clock cycles)
// when outputting the frame
void expandFrame(uint8_t LED_Frame[], uint8_t expanded_LED_frame[]) {
    for (int i = 0; i < FRAME_SIZE; i++) {
        for (int bit = 0; bit < 8; bit++) {
        	expanded_LED_frame[i*8 + bit] = (LED_Frame[i] >> (7-bit)) & 1;
            // use (7-bit) if you want MSB first
        }
    }
}

int outputFrame(uint8_t LED_Frame[]){
	uint8_t expanded_LED_Frame[FRAME_SIZE*8];
	expandFrame(LED_Frame, expanded_LED_Frame);

	for(uint16_t i = 0; i < (FRAME_SIZE*8); i++) {
		if(expanded_LED_Frame[i] == 1) {
			Output1();
		} else {
			Output0();
		}
	}
	return 0;
}

int main(void)
{
	uint8_t LED_Frame_Ones[FRAME_SIZE] =
	{0xF, 0xF, 0xF, 0xF, 0xF, 0xF, 0xF, 0xF, 0xF, 0xF,
	 0xF, 0xF, 0xF, 0xF, 0xF, 0xF, 0xF, 0xF, 0xF, 0xF,
	 0xF, 0xF, 0xF, 0xF, 0xF, 0xF, 0xF};

	uint8_t LED_Frame_Zeroes[FRAME_SIZE] =
	{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
	 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
	 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0};

  HAL_Init();

  SystemClock_Config();
  MX_GPIO_Init();

  while (1)
  {
	  outputFrame(LED_Frame_Ones);
	  HAL_Delay(500);
  }

}

void SystemClock_Config(void)
{
  RCC_OscInitTypeDef RCC_OscInitStruct = {0};
  RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};

  __HAL_FLASH_SET_LATENCY(FLASH_LATENCY_0);

  RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSI;
  RCC_OscInitStruct.HSIState = RCC_HSI_ON;
  RCC_OscInitStruct.HSIDiv = RCC_HSI_DIV4;
  RCC_OscInitStruct.HSICalibrationValue = RCC_HSICALIBRATION_DEFAULT;
  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_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_HSI;
  RCC_ClkInitStruct.SYSCLKDivider = RCC_SYSCLK_DIV1;
  RCC_ClkInitStruct.AHBCLKDivider = RCC_HCLK_DIV1;
  RCC_ClkInitStruct.APB1CLKDivider = RCC_APB1_DIV1;

  if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_0) != HAL_OK)
  {
    Error_Handler();
  }
}

static void MX_GPIO_Init(void)
{
  GPIO_InitTypeDef GPIO_InitStruct = {0};
  /* USER CODE BEGIN MX_GPIO_Init_1 */

  /* USER CODE END MX_GPIO_Init_1 */

  /* GPIO Ports Clock Enable */
  __HAL_RCC_GPIOC_CLK_ENABLE();
  __HAL_RCC_GPIOA_CLK_ENABLE();

  /*Configure GPIO pin Output Level */
  HAL_GPIO_WritePin(TestPoint_GPIO_Port, TestPoint_Pin, GPIO_PIN_RESET);

  /*Configure GPIO pin Output Level */
  HAL_GPIO_WritePin(DOUT_GPIO_Port, DOUT_Pin, GPIO_PIN_RESET);

  /*Configure GPIO pin : TestPoint_Pin */
  GPIO_InitStruct.Pin = TestPoint_Pin;
  GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
  GPIO_InitStruct.Pull = GPIO_NOPULL;
  GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
  HAL_GPIO_Init(TestPoint_GPIO_Port, &GPIO_InitStruct);

  /*Configure GPIO pin : DOUT_Pin */
  GPIO_InitStruct.Pin = DOUT_Pin;
  GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
  GPIO_InitStruct.Pull = GPIO_NOPULL;
  GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
  HAL_GPIO_Init(DOUT_GPIO_Port, &GPIO_InitStruct);

  /*Configure GPIO pin : BTN_Pin */
  GPIO_InitStruct.Pin = BTN_Pin;
  GPIO_InitStruct.Mode = GPIO_MODE_INPUT;
  GPIO_InitStruct.Pull = GPIO_NOPULL;
  HAL_GPIO_Init(BTN_GPIO_Port, &GPIO_InitStruct);

  /**/
  HAL_SYSCFG_SetPinBinding(HAL_BIND_SO8_PIN1_PC14|HAL_BIND_SO8_PIN5_PA8);
}

void Error_Handler(void)
{
  __disable_irq();
  while (1)
  {
  }
}
#ifdef USE_FULL_ASSERT

void assert_failed(uint8_t *file, uint32_t line)
{
}
#endif

 

1 REPLY 1
gbm
Principal

Use UART or SPI to generate the WS2812 data stream in hardware. Also, no need for a level translator - configure the pin as open drain and use external pullup resistor of 1k5..4k7 to +5V.

HAL uses SysTick interrupt, so any attempt to use software timing will fail at some point (every millisecond to be precise).

My STM32 stuff on github - compact USB device stack and more: https://github.com/gbm-ii/gbmUSBdevice