euraad
euraad

Reputation: 2856

How to make a proper delay in a microcontroller?

How to a make a proper delay inside a microcontroller?

I see a lot of people using a basic for-loop

for(int i = 0; i < 100000; i++){}

As a delay, but how can I create a proper delay inside of a microcontroller? If I can take an example. Let's assume that I'm going to use SysTick in STM32 and I want to activate the SysTick register. Which register should I use then?

I have the STM32F401RE processor and here is the clock tree.

enter image description here https://www.st.com/resource/en/reference_manual/rm0368-stm32f401xbc-and-stm32f401xde-advanced-armbased-32bit-mcus-stmicroelectronics.pdf

According to page 95

The RCC feeds the external clock of the Cortex System Timer (SysTick) with the AHB clock 
(HCLK) divided by 8. The SysTick can work either with this clock or with the Cortex clock 
(HCLK), configurable in the SysTick control and status register

So that means? How can I activate SysTick?

Upvotes: -2

Views: 623

Answers (2)

wek
wek

Reputation: 1213

I have the STM32F401RE processor ...

STM32 is not a processor but an SoC, stitched together from various components, one of which is the processor (in your case Cortex-M4). That is not provided by ST, but ARM, and it has a separate sets of documents. SysTick is part of the processor, so its description is not in the STM32F4xx Reference Manual to which you've linked, but in so-called STM32 Cortex-M4 Programming Manual (see chapter 4.5 SysTick timer (STK) therein); and, ultimately, in ARM's Technical Reference Manual to the Cortex-M4 processor.

Upvotes: 1

old_timer
old_timer

Reputation: 71596

A counter loop is a/the correct way to kill time for the hello world equivalent for an mcu. Blink a LED. Getting the gpio enabled and making a pin an output and toggling that pin is more the enough of a recipe for failure than to add on more peripherals and clock domains, etc. You have to only be careful not to make that counter loop dead code which can be done various ways. Most will say to use volatile for the loop variable, some will just not optimized, I prefer to call a function outside the optimization domain.

for(rx=0;;rx++)
{
    PUT32(GPIOA_BSRR,((1<<5)<<0));
    for(ra=0;ra<200000;ra++) dummy(ra);
    PUT32(GPIOA_BSRR,((1<<5)<<16));
    for(ra=0;ra<200000;ra++) dummy(ra);
}


.thumb_func
.globl dummy
dummy:
    bx lr

As previously answered, before you even start this, the first thing you do when you get a new chip eval board or breakout board is get all the documentation. ST makes chips ARM makes IP not chips, many vendors buy ARM IP to put in their chips, it is not an arm chip it is an "arm based" chip.

The chip part number STM32F401RE ideally leads you to st's website and a search will find information about that part. From ST what you want is the datasheet for that part and the reference manual, you do not really need anything else. The st documentation will describe this product as using an ARM cortex-m4. So as answered, you go to arm and look for cortex-m4 and you are looking for the TRM or technical reference manual, this describes the core, ideally you want to match revisions, which is often not documented and you have to back your way into that well down the road when you can "see" better into the core to read its revision. I will incorrectly say that most of these cores have one revision that everyone ended up buying and that is the current revision. The cortex-m4 TRM will indicate that it is based on the armv7-m architecture, so you go back to arm and find the ARM ARM for the armv7-m. The architectural reference manual. This describes the overall architecture and instruction set and such, how it boots, etc. The combination of the arm arm and trm are what you need to manage the arm ip purchased by st and used in this stm32.

The st reference manual will likely not document the systick timer as it is not st ip. Some vendors do, but you should quickly realize maybe this was in the arm documentation (clearly having all four documents open for some number of hours days as you work through figuring all of this out even including an initial counter based led blinker (also the schematic and other documentation related to the board that contains the mcu).

The systick timer is very easy to use, note that, as it should be documented, it is not hard wired to the arm processor clock but is a separate clock source to that arm ip, many vendors wire it to the same clock some run it through a fixed divisor like a divide by 2. This is important in the programs that follow the initial counter based led blinker.

The systick timer and other timers from st in this part, can be used free running meaning either count from all ones down to zero then roll over to all ones infinitely or count from zero to all ones and then roll over to zero. Some you can control up/down others you can not, systick you cannot.

One initial way to use the timer and to serve another important step in the bring up path for your understanding of an mcu is.

PUT32(STK_CSR,0x00000004);
PUT32(STK_RVR,0xFFFFFFFF);
PUT32(STK_CSR,0x00000005);

...

for(rx=0;rx<5;rx++)
{
    led_on();
    while(1) if(GET32(STK_CVR)&0x200000) break;
    led_off();
    while(1) if((GET32(STK_CVR)&0x200000)==0) break;
}

A particular bit in the count will be a power of two of the clock it is being fed. From this you can ideally visually see with human eyes the led turning on and off, as well as if you make it slow enough compare it against a wall clock (stopwatch) (making the blink rate ideally several seconds or tens of seconds between state changes). This is to try to figure out what the systick timer clock rate really is as there are divide by twos and such that are not documented sometimes that will bite you. All documents are buggy, use them with that fact in mind.

Remember, as documented, the systick timer is 24 bits not 32, so while searching for a single bit in the count that has a satisfying blink rate, you likely cannot go so slow as to get an accurate stopwatch timing, but...you can try...(there are tricks you can figure out in order to do this if you wish, but...)..

I clearly figured out the part I was using was likely clocked at 16Mhz in the RC timer built into the chip (I completely controlled the boot, no clock manipulation, etc, just boot, set gpio output, start systick, blink).

void do_delay ( unsigned int sec )
{
    unsigned int ra,rb,rc,rd;

    rb=GET32(STK_CVR);
    for(rd=0;rd<sec;)
    {
        ra=GET32(STK_CVR);
        rc=(rb-ra)&0x00FFFFFF;
        if(rc>=16000000)
        {
            rb=ra;
            rd++;
        }
    }
}

The key point here is that because it is a 24 bit timer using 32 bit variables, you need to mask the result to get an accurate begin to end count. It will give strange results if you do not do this mask. And should also indicate use of timers in this way for begin to end timing on a free running timer (best setup for doing a then to now measurement) to mask, so if a 16 bit timer or 8 bit with 32 bit variables use the appropriate mask after the subtraction.

The next to last thing to do with the systick timer is to set the timer not to free run but count X number of ticks.

PUT32(STK_CSR,4);
PUT32(STK_RVR,1000000-1);
PUT32(STK_CVR,0x00000000);
PUT32(STK_CSR,5);

for(rx=0;;rx++)
{
    PUT32(GPIOBBASE+0x18,(1<<3));
    delay(50);
    PUT32(GPIOBBASE+0x18,(1<<(3+16)));
    delay(50);
}

int delay ( unsigned int n )
{
    unsigned int ra;

    while(n--)
    {
        while(1)
        {
            ra=GET32(STK_CSR);
            if(ra&(1<<16)) break;
        }
    }
    return(0);
}

This one, another stm32. The systick is set to count 1 million clocks (yes you put an n-1 there, you can use an example like this and against a stop watch see this error if you put N rather than N-1. Most timers and dividers this is how you use them) and then that delay is set to count to 50 times one million. The timer is used to count down from one million, there is a flag that is also tied to the interrupt (the last thing you want to do, and only do as needed, interrupts must always have a justification for use).

The last way would be to setup the systick interrupt, the cortex-m is interesting and nice that it is designed to not need an assembly wrapper around a C function used as an interrupt handler, you can put the C label/address right in the vector table, also there are 128 or more individual interrupts to reduce the time it takes to figure out the source of the interrupt, etc. Traditionally, all processors not just mcus, you need to save state (preserve the general purpose registers and psr to the stack) and often have a specific return from interrupt instruction, so you have to wrap your C handler function in assembly language or have a custom compiler built for this target that then has custom directives to cause the compiler to do this for you. The cortex-m as documented in the ARM ARM, saves state for you and is designed specifically around handlers that conform to arms calling convention which gcc and clang do.

.cpu cortex-m0
.thumb

.thumb_func
.global _start
_start:
stacktop: .word 0x20001000
.word reset
.word hang
.word hang
.word hang
.word hang
.word hang
.word hang
.word hang
.word hang
.word hang
.word hang
.word hang
.word hang
.word hang
.word systick_handler
...

.thumb_func
reset:
    bl notmain
    b hang
.thumb_func
hang:   b .


.thumb_func
.globl PUT32
PUT32:
    str r1,[r0]
    bx lr
.thumb_func
.globl GET32
GET32:
    ldr r0,[r0]
    bx lr

volatile unsigned int tick;
void systick_handler ( void )
{
    GET32(STK_CSR);
    if((tick++)&1) led_on();
    else     led_off();
}

PUT32(STK_CSR,0x00000004);
PUT32(STK_RVR,8000000-1);
PUT32(STK_CVR,8000000-1);
PUT32(STK_CSR,0x00000007);

Code from a working STM32F401RE example that uses the sytick timer with an interrupt.

Yes, this is not the proper use of volatile within the C language, not its intent, and not expected to work. But it works here (disassemble often to see that the compiler is doing what you want, particularly with bare-metal work as it will do the wrong thing especially with the risky things that vendor libraries do in their code). And can change that mask to make the blink slower as written.

Getting up to but not including the systick event/interrupt may seem silly and too many steps, but just getting booted to enable the gpio to enable the output pin and turning an led on (read your schematic you may have to turn the pin "off" to turn the led "on) is monumental. The number of failure points from linker script, bootstrap, calling the C entry point, talking to registers from that program, etc. Countless folks fail and give up. So that accomplishment, with or with help from internet examples is a major boost. Then spending a little time playing with a very simple peripheral (systick timer) generates more personal victories and confidence to try something else like using one of STs timers in that part. Which involves enabling the timer figuring out through the pages of features of how that timer works to get it to just be a stupid free-running timer, up or down, then repeat the above. Using interrupts from that timer being much more work than for systick which one might argue is an event not an interrupt.

Then ideally from the timer you learn more about the real clocking scheme (finding the occasional un or poorly documented divide by two in some clock branch) and then aim for the uart. Your number one debug/development interface (blinking an led being number two, using a gpio to trigger a scope being three, a scope in general being near the top at all times).

Upvotes: 0

Related Questions