In the previous post, the STM32 development board was turning a LED on and off in response to a button press. Not very exciting but satisfying anyway. Next, I want to have a look at setting up the system oscillator and the systick timer…

In the last example, nothing was done to set up the oscillator or determine where the processor got its clock source from. There is a moderately complex set of clocking arrangements possible with the STM32. Three sources can be used for the System Clock (SYSCLK) – the High Speed Internal clock (HSI), the High Speed External clock (HSE) and the Phase Locked Loop clock (PLL). After a reset the device will naturally boot up using the HSI clock. This is an RC oscillator that runs at a nominal 8MHz. The accuracy is supposed to be about 1% and this may well be enough for many uses. It has the advantage of requiring no external components but its use limits the maximum available SYSCLK value to 64MHz. With an external crystal or resonator, the maximum possible system clock is 72MHz.

A fast SYSCLK is not an obviously good thing. Access to programs stored in flash requires wait states to be added if the value of SYSCLK is set too high. A prefetch buffer must be used when SYSCLK is greater than 24MHz.

Even without an external crystal or resonator, the PLL can be used to multiply the clock by an integer between 3 and 16.

However, before messing with the clock source, lets have a look at a way to generate fixed time intervals based on the clock so that we can see the effect of any change. Software timing loops are, of course, useless for this as they depend on things like the optimisation level set on the compiler.

The STM23, in common with the other CORTEX-M3 devices, can generate an internal clock called SysTick. This can then be used to trigger interrupts which will form the heartbeat of the system.

The Cortex System Timer, SysTick, is generated by a divider, of ration 1/8, fed from the AHB clock which is in turn derived from the SYSCLK via a divider. Left without any other configuration, the AHB divider will be set to one so the SysTick frequency will be SYSCLK/8. With the default 8MHz HSI clock running, SysTick should be 1MHz. And yes, this is all a bit tricky to keep track of.

Now, SysTick can trigger an interrupt so we need to enable that interrupt and provide an interrupt handler to do something on each tick. An interrupt handler looks like this:


void SysTick_Handler(void)
{
ticks++;
if (ticks & 1) {
set_leds(1);
} else {
set_leds(0);
}
}

Notice that the handler is a simple void function with no arguments. Crossworks pre-defines all the handlers with a ‘weak’ attribute, allowing them to be simply replaced by your code when the linker works its magic. This simple handler maintains a global variable, tickCount, that can be used for accurate timing elsewhere in the program.

Now we have to turn on the interrupt and make sure that SysTick is generated at an appropriate frequency. The code required can be placed in a function called SystemInit() which will be one of the first things called in main():


void SystemInit()
{
SystemFrequency = 8000000;
SysTick_LOAD = 1000; // reload the counter every 1000 microseconds
SysTick_CTRL |= SysTick_CTRL_ENABLE; // turn on the counter
SysTick_CTRL |= SysTick_CTRL_TICKINT; // enable the interrupt
}

Turning on the counter is not enough to enable the interrupt. That requires a separate action. If all that is configured correctly, the result should be that the LED on my board will be turned on and off with a frequency of 500Hz – on for 1ms, off for 1ms. According to my trusty oscilloscope, the actual frequency is 499.4 Hz. Pretty good since, with 1% accuracy, I might expect it to be anywhere between 495Hz and 505Hz.

At this point, the clock arrangement is:

Which is pretty simple and gives us a nice, working system right out of the box. Unfortunately, it is running at only 8MHz and the Cortex can do much better than that. Another look at the clock tree in the manual gives the possibility of putting the HSI through a Phase Locked Loop multiplier. For some reason, the HSI clock has to pass through a /2 divider first but, suppose we want a 20MHz clock. That would mean 8MHz first divided by 2 and then multiplied by 5:

Note that the input to sysTick would be 2.5MHz so that the reload count needed to restore the 1ms SysTick would be 2500.

The first job is to tell the PLL input selector to use the HSI input. Then, the PLL multiplier must be set to 5 and we have to wait for the PLL to settle. Finally, we can select the PLL output as the source for SYSCLK.

Here is the new SystemInit() code:


void SystemInit()
{
RCC_CR |= RCC_CR_HSEON;
while ((RCC_CR & RCC_CR_HSERDY)==0);
RCC_CFGR |= (1 < < RCC_CFGR_PLLXTPRE_BIT); // pre-scale the HSE by 2
RCC_CFGR |= (1 << RCC_CFGR_PLLSRC_BIT); // use the HSE as input to the PLL
FLASH_ACR = FLASH_ACR_PRFTBE | 2< RCC_CFGR = PLL_MUL_X16 << RCC_CFGR_PLLMUL_BIT; // set the multiplier to be x5
RCC_CR |= RCC_CR_PLLON; // enable the PLL
while ((RCC_CR & RCC_CR_PLLRDY)==0); // wait for it to become stable
RCC_CFGR = (RCC_CFGR & ~RCC_CFGR_SW_MASK) | 0x02; // switch to PLL
while ((RCC_CFGR & (2< SystemFrequency = 16L*8000000L / 2;
SysTick_LOAD = SystemFrequency / 8 / 1000;
SysTick_CTRL |= SysTick_CTRL_ENABLE; // turn on the counter
SysTick_CTRL |= SysTick_CTRL_TICKINT; // enable the interrupt
}

There are several more clock systems and subsystems. The Reference Manual tells you all about them but is short of code on how to set it all up. Keep it all simple if possible. Next time, I will set the clock as high as it will go and look at the effect on the code and performance.

 

This Post Has 12 Comments

  1. Suresh

    Dear Peteh,

    In the bellow program, the systick_handler() is not called at all. The program suppose to call once in one mSec. How the systick_Handler() address will be updated in IVT? The crossworks will do automatically? kindly mail me your valuable input. Thanks in advance.

    with regards,
    Suresh M

    #include <targetsSTM32F10x.h> // global variables volatile unsigned int tickCount; void SysTick_Handler(void) { tickCount++; if (tickCount & 1) { //set_leds(1); GPIOC_BRR = 0x1000; } else { //set_leds(0); GPIOC_BSRR = 0x00001000; // set the portC.pin12 } } void MySysInit() { RCC_CR |=RCC_CR_HSEON; while(!(RCC_CR & RCC_CR_HSERDY)); // wait until external crystal osc is ready FLASH_ACR = FLASH_ACR_PRFTBE | 2<<FLASH_ACR_LATENCY_BIT; // 2 wait states RCC_CFGR = 7<<RCC_CFGR_PLLMUL_BIT | 1<<RCC_CFGR_PLLSRC_BIT; // X9 RCC_CR |= RCC_CR_PLLON; while ((RCC_CR & RCC_CR_PLLRDY)==0); RCC_CFGR |= 2; RCC_CFGR |= 3<<RCC_CFGR_ADCPRE_BIT; // ADC clock source is 72/8 MHz while ((RCC_CFGR & (2<<RCC_CFGR_SWS_BIT))==0); // switch to pll //SystemFrequency = 72000000; SysTick_LOAD = 9000; // reload the counter every 1000 microseconds SysTick_CTRL |= SysTick_CTRL_ENABLE; // turn on the counter SysTick_CTRL |= SysTick_CTRL_TICKINT; // enable the interrupt } main() { int LED_status=0; MySysInit(); RCC_APB2ENR = RCC_APB2ENR_IOPAEN; // portA enable RCC_APB2ENR |= RCC_APB2ENR_IOPCEN; // portC enable RCC_APB2ENR |= RCC_APB2ENR_IOPBEN; // portB enable GPIOC_CRH = 0x00060000; // portC. pin 12 as open drain GPIOA_CRH = 0x00000060; //PORTA pin 9 as open drain GPIOA_CRL = 0x00000008; // PORTA Pin 0 as input push pull GPIOB_CRH = 0x00000008; // PORTB pin 8 as input GPIOC_BRR = 0x1000; // reset PortC.pin12 GPIOA_BSRR = 0x00000200; // PORTA pin 9 LED OFF while(1) { if((!(GPIOA_IDR & 0x1))|(!(GPIOB_IDR & 0x100))) { // key press & Toggle LED if(LED_status) { GPIOA_BRR=0x0200; LED_status =0; } else { LED_status=1; GPIOA_BSRR = 0x00000200; } } } }

  2. peteh

    I don’t know what is going on here.

    When I run the code on actual hardware, it behaves just as it should. When I run it on the simulator, the interrupt does not get called. This is because the SysTick counter is not being decremented. I have no idea why.

  3. Suresh

    In the RAM debug mode I traced the code, It’s entering into the infinite loop, by calling the DEFAULT_ISR_Handler. The IVT has address of DEFAULT_ISR_HANDLER rather than my SysTickHndler(). how to change the IVT?

    DEFAULT_ISR_HANDLER SysTick_Handler 0x20000254: E7FE b 0x20000254 <+0xa3ca5fab>
    Thanks in advance.

    with regards,
    Suresh M

  4. Suresh

    Now it is working. I compiled as .cpp file. Hence it’s hooking the systick to default handler, which is a infinite loop. Once I changed to .c file, it’s working fine. Thanks Peteh once again.

    I want to try for virtual com in STM32. If any application note or driver stack for Crossworks is available? Thanks in advance.

  5. peteh

    Well, that is a good tip to remember. I need to look into building C++ code.

    What do you mean by ‘virtual com’?

  6. Shannon

    Suresh, (or Pete but I think he’s at APEC)

    I was told by Rowley that the simulator does not simulate the systick counter. Does your counter count in simulation? I ask because I ran the systick example from ST and it builds fine but does not work in simulation. Rowley said that it was because their simulator doesn’t deal with systick.

    Shannon

  7. Shannon

    Suresh, (or Pete but I think he’s at APEC) I was told by Rowley that the simulator does not simulate the systick counter. Does your counter count in simulation? I ask because I ran the systick example from ST and it builds fine but does not work in simulation. Rowley said that it was because their simulator doesn’t deal with systick. Shannon

  8. peteh

    I shall have to look at the Rowley documentation when I get home. (Yes, I am in sunny Palm Springs and will write that up in a day or two).

  9. peteh

    I just looked on the Rowley site and cannot find reference to systick – do I need to look harder or can you point me to some reference describing this?

  10. Shannon

    I spoke with Michael Johnson directly via email. His responses I’ve copied below:

    The simulator doesn’t simulate the systick timer.

    Regards
    Michael

    Feb-22 2010 08:23 am.

    …and in response to me asking if there are plans to simulate systick in the future:

    I’d like to do this, the hard bit is keeping the timer synchronised with the instruction simulation.

    Feb-24 2010 08:46 am.

  11. Anonymous

    Any idea how to use SysTick_Handler() in the .cpp file. I have the same issue with NXP LPC1768. In the .map file the interrupt is not re-directed when compiling source file as .cpp. .map file is correct when project is compiled as .c Help please!?!

  12. Suresh

    When I called Crosswork support, they replied like bellow. But I didn’t tried. You can try this…

    You need to use

    extern “C” {
    MySysTickHandler()
    }

    if you are compiling with the c++ compile.

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.