0
\$\begingroup\$

On an STM32F103C8T6 (72MHz) I want to use a delay in range of 100's of nanosecond (to create a LED strip driver, WS2813B).

Since HAL only provides a ms delay, I found at various places code to use the ticks/debug counter as timer.

I have the code below:

Initialization:

volatile unsigned int *DWT_CYCCNT = (volatile unsigned int *)0xE0001004; //address of the register
volatile unsigned int *DWT_CONTROL = (volatile unsigned int *)0xE0001000; //address of the register
volatile unsigned int *SCB_DEMCR = (volatile unsigned int *)0xE000EDFC; //address of the register

unsigned int count = 0;

Within main():

  *SCB_DEMCR = *SCB_DEMCR | 0x01000000;
  *DWT_CYCCNT = 0; // reset the counter
  *DWT_CONTROL = *DWT_CONTROL | 1 ; // enable the counter

  int ticks = 1;

  while (1)
  {
      unsigned int start, current;
      start = *DWT_CYCCNT;
      do
      {
          current = *DWT_CYCCNT;
      } while((current - start) < ticks);

  if (count++ % 1000 == 0)
  {
         HAL_GPIO_TogglePin(GPIOB, GPIO_PIN_6);
  }
}

When I connect a logic analyzer, I see a PWM signal of 0.53 ms per toggle. Since it is toggled every 1,000 counts, this means 0.53 us (530 ns). However, with 1 tick, I would expect something like 1/72,000,000 = 14 ns (plus some overhead for the while loop/counter). (note: the counter for 1,000 I used because my logic analyzer can only handle 200 kHz).

I'm not sure if the addresses in the initialization are correct (found them only for STM32F103VET6).

So my question: What is the mistake in the program above?

(side question: is this method good anyway for a LED driver, or should I send the data by interrupt or DMA?).

Update

The reason I need these kind of delays are for the T0H, T1H, T0L and T1L times which can be found at WS2813B datasheet, page 5.

Data Transfer Time (TH+TL=1.25µs±300ns)

T0H 0-code, High-level time 300ns~450ns

T1H 1-code, High-level time 750ns~1000ns

T0L 0-code, Low-level time 300ns~100us

T1L 1-code, Low-level time 300ns~100us

RES Frame unit, Low-level time 300µs or more

\$\endgroup\$
  • \$\begingroup\$ I can't wrap my head around how the µC and LED strip driver are related, or why you feel that you need 100 ns resolution for something. I'd love to hear what your final goal is. Is your final goal perhaps to make 10 LEDs in series to glow brighter/weaker in 1 second oscillation like some LED Christmas strips? \$\endgroup\$ – Harry Svensson Apr 11 '18 at 23:43
  • \$\begingroup\$ @HarrySvensson I updated my question (see update, last part), that shows I need different delays depending on a 0 or 1. However, I could use e.g. a 400 ns 'fixed' time in case I need a timer. \$\endgroup\$ – Michel Keijzers Apr 12 '18 at 2:57
  • \$\begingroup\$ Have you already tried the Systick handler? I think you can configure this timer and achieve the desired delay. \$\endgroup\$ – Abel Tom Apr 12 '18 at 6:21
  • \$\begingroup\$ @AbelTom I have not tried, I will do in case the SPI solution will not work. \$\endgroup\$ – Michel Keijzers Apr 12 '18 at 9:34
  • \$\begingroup\$ Somewhat unrelated, but in your initialization section those int * should be int *const \$\endgroup\$ – Reinderien May 29 '18 at 8:14
4
\$\begingroup\$

Running at 72 MHz one instruction cycle is ~ 14 ns. Instruction timing is roughly 1 instruction per cycle, but memory access, the peripheral bus, branch penalty, and flash timing all get in the way. To get an external waveform that is accurate enough for a single WS2813B is doable in assembler, but for a chain of these LEDs it is a very tough (or impossible) job.

Your best chance is to (mis) use the SPI peripheral, and feed it bytes that are pre-calculated to produce the desired waveform.

Or switch to the WS2801, which has separate clock and data lines.


update:

The advantage of SPI is that there is some buffering - IIRC you can load one byte while the other is being shifted out, and the hardware will take care of putting the bytes immediately after each other. This takes most of the pain out of the timing.

One of my students actually did this for his end-of-first-year project, but that was on an Arduino Due (also Cortex M3, but at 92 MHz, and with a different SPI peripheral). He first tried to get the timing right uysing tweaked C++, but that was too fragile and could not handle a longer chain.

\$\endgroup\$
  • \$\begingroup\$ Without DMA I think the time to load the new word is prohibitive. \$\endgroup\$ – Jan Dorniak Apr 12 '18 at 8:08
  • \$\begingroup\$ Thanks for this answer, I didn't realize the overhead would be that much. So far I want to use only one chain, but I like the SPI solution. It will cost some more memory (since I have to keep the chain of bits ready beforehand), but even the STM32F103C8T6 (20 KB) has enough to run several strips in case needed in future. \$\endgroup\$ – Michel Keijzers Apr 12 '18 at 9:08
  • \$\begingroup\$ @JanDorniak Why? My plan was (after reading the answer), to create a big array containing all data, than send it via SPI (non DMA). Than wait until data changes, and send it again for the next change. So the words are loaded 'slowly' but send with one command (for sending it via SPI). \$\endgroup\$ – Michel Keijzers Apr 12 '18 at 9:11
  • \$\begingroup\$ Btw, I explicitly selected WS2813 above WS2812 for the fact that if one LED breaks, I can use the remainder LEDs. I would have to check what differences are with WS2801. \$\endgroup\$ – Michel Keijzers Apr 12 '18 at 9:12
  • 1
    \$\begingroup\$ @MichelKeijzers Read up the manual. HAL obfuscates a lot of stuff, the data on SPI is actually sent using 8 or 16 bit words and after writing one word the processor must load the next one into the perfieral register which may take too long. Seeing as you seem to have an oscilloscope handy - do it and check the results. \$\endgroup\$ – Jan Dorniak Apr 12 '18 at 9:16
2
\$\begingroup\$

You can drive the WS leds using DMA and timer generated PWM signal. Read about the DMAR register and how to use it in the RM. I do not know if it is possible using the HAL library as I do not use it.

Bare register is easy and straightforward.

\$\endgroup\$
  • \$\begingroup\$ Thanks, the problelm is that the PWM signal should change often, but maybe it could work. At least another option to try if other fails). \$\endgroup\$ – Michel Keijzers Apr 12 '18 at 10:12
  • \$\begingroup\$ @MichelKeijzers I use this approach all the time without any problems. I do not have a code to share as I do not use the F1 family anymore - F3 is cheap but much better and modern. \$\endgroup\$ – P__J__ Apr 12 '18 at 10:18
  • \$\begingroup\$ Ok ... I like HAL so far, still it's a huge dive from the Arduino. I would need to check for F3, I know they exist but haven't them encountered at e.g. AliExpress. I have some F4 but intending to use them for something else later. \$\endgroup\$ – Michel Keijzers Apr 12 '18 at 10:31
  • \$\begingroup\$ I found out that HAL sometimes extends a clock pulse (including data), so I cannot use it. Not sure if it is HAL or not. I could check DMAR/RM but doubt it is supported (easily) by HAL. Instead I'm going to use a timer directly, either with DMA or not. \$\endgroup\$ – Michel Keijzers Apr 14 '18 at 11:25

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.