CPU load monitor for STM32F4xx
I’ve been searching for a simple CPU load monitor on STM32F4xx device. Today, I’ve managed to get one very, very primitive CPU load monitor for these devices.
Check library here.
STM32F4xx have built-in DWT (Data Watchpoint and Trace unit) which has several timers there. 2 of them are cycle counter and sleep counter. The first counts number of CPU cycles and the second counts number of cycles where CPU was sleeping.
It would be very easy to get CPU load, in case that cycle counter and sleep counter were the same. Then, equation for CPU load would be:
CPU_LOADÂ [%] = (cycle_counter / (cycle_counter + sleep_counter)) * 100%
And that’s all in a case that you CPU is going to sleep mode every time it does not work on any task. If this is not true then this CPU load method will not work.
As I mentioned above, equation would match in case that timers are the same, but the problem is, that cycle counter 32-bits register and sleep counter is only 8-bits timer. So this timer will overlap at least 100times before you will be able to read anything useful from it. So, with proper method you can still measure sleep time using normal cycle counter.
We have next problem. If we read cycle counter before WFI instruction and after WFI we read register again, do substract, we will also measure a time we were inside interrupt routine which wake up CPU. The workaround of this problem is, that you do these steps:
- Disable all interrupts
- Read cycle counter and store it to variable
- Go to sleep mode using __WFI() or __WFE()
- Read cycle counter again and do substract ob previous read before sleep
- Enable interrupts, program will jump to interrupt handler which woke up CPU
Note: Without wake up source, you will not be able to measure anything.
Example
To run example below, I used Nucleo-F401RE board with 84MHz CPU speed:
- CPU checks inside main if button is pressed
- If it is pressed, then it does some useless looping to load some CPU
- It toggles LED
- It goes to sleep mode
- Systick is a timer which wakes up timer in periods of 1ms
- Each second, result is printed via USART
Note:Â I had some problems with all my boards where ST-Link is installed that after programming, DWT timer did not work. And then I got some really strange results because I had division with zero. In this case, remove power from board and put again. Then it will work.
Driving this examples I got these results:
- When button was not pressed, CPU load was 0.18%. Everything what CPU needs to do is to checks if button is pressed, toggle led and write some data with printf function
- When button was pressed, CPU load was 51.37%. This time, CPU did some useless counting which makes him very busy in relation to when button was not pressed.
Running example above, you will need these libraries:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 |
/** * Keil project for very basic and primitive CPU load monitor * * Before you start, select your target, on the right of the "Load" button * * @author Tilen Majerle * @email tilen@majerle.eu * @website http://stm32f4-discovery.net * @ide Keil uVision 5 * @conf PLL parameters are set in "Options for Target" -> "C/C++" -> "Defines" * @packs STM32F4xx Keil packs version 2.4.0 or greater required * @stdperiph STM32F4xx Standard peripheral drivers version 1.5.0 or greater required */ /* Include core modules */ #include "stm32f4xx.h" /* Include my libraries here */ #include "defines.h" #include "tm_stm32f4_delay.h" #include "tm_stm32f4_disco.h" #include "tm_stm32f4_general.h" #include "tm_stm32f4_usart.h" #include "stdio.h" /* Go to sleep mode */ void GoToSleepMode(void); int main(void) { __IO uint32_t i; /* Initialize system */ SystemInit(); /* Init DWT timer to count processor cycles */ TM_GENERAL_DWTCounterEnable(); /* Reset counter */ TM_GENERAL_DWTCounterSetValue(0); /* Initialize delay */ TM_DELAY_Init(); /* Initialize leds on board */ TM_DISCO_LedInit(); /* Initialize button */ TM_DISCO_ButtonInit(); /* Init USART2, TX: PA2, RX: PA3, 921600 baud */ TM_USART_Init(USART2, TM_USART_PinsPack_1, 921600); while (1) { /* If button is pressed, do some useless dummy counting */ /* Otherwise, go sleep directly */ if (TM_DISCO_ButtonPressed()) { /* Do some while looping */ i = 0; while (i++ < 0x2FFF); } /* Toggle leds */ TM_DISCO_LedToggle(LED_ALL); /* Go to sleep mode */ GoToSleepMode(); } } void GoToSleepMode(void) { uint32_t t; static uint32_t l = 0; static uint32_t WorkingTime = 0; static uint32_t SleepingTime = 0; static uint32_t LastTime = 0; /* Disable interrupts */ __disable_irq(); /* Add to working time */ WorkingTime += DWT->CYCCNT - l; /* Save count cycle time */ t = DWT->CYCCNT; /* Go to sleep mode */ /* Wait for wake up interrupt, systick can do it too */ __WFI(); /* Increase number of sleeping time in CPU cycles */ SleepingTime += DWT->CYCCNT - t; /* Save current time to get number of working CPU cycles */ l = DWT->CYCCNT; /* Enable interrupts, process/execute an interrupt routine which wake up CPU */ __enable_irq(); /* Every 1000ms print CPU load via USART */ if ((TM_DELAY_Time() - LastTime) >= 1000) { /* Save new time */ LastTime = TM_DELAY_Time(); /* Print to user */ printf("W: %u; S: %u; L: %5.2f %%\n", WorkingTime, SleepingTime, ((float)WorkingTime / (float)(SleepingTime + WorkingTime) * 100)); /* Reset time */ SleepingTime = 0; WorkingTime = 0; } } /* Handle printf functionality */ int fputc(int ch, FILE* fil) { /* Send character over USART */ TM_USART_Putc(USART2, ch); /* Return character */ return ch; } |
Recent comments