Crossworks Blinky Project 2 - RCC and Systick
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<<FLASH_ACR_LATENCY_BIT; // 2 wait states
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<<RCC_CFGR_SWS_BIT))==0); // wait until it is ready
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.
- ARM ,
- STM32 ,
- Systick ,
- STM32 ,
- crossworks

Comments
systick_Handler not called .. IRQ is not raised?
#include <targets\STM32F10x.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;
}
}
}
}
How annoying!
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.
systick_Handler not called ..
DEFAULT_ISR_HANDLER SysTick_Handler0x20000254: E7FE b 0x20000254 <+0xa3ca5fab>
Now it is working. I compiled
Excellent.
Confused
Confused
That would explain a lot.
Do you have a reference?
Direct email
Any idea how to use
When I called Crosswork
Post new comment