Ten post chciałbym poświęcić na opisanie sprzętowego licznika zegarowego DWT CYCCNT. Jest to bardzo precyzyjny układ sprzętowy za którego pomocą można dokonać bardzo dokładnych obliczeń. Rejestr CYCCNT zlicza ilość cykli wykonanych przez CPU. W związku z czym można go użyć do obliczenia dokładnej ilości cykli w danym czasie.
[Źródło: http://www.st.com/en/evaluation-tools/stm32f4discovery.html]
Jego minusem zwłaszcza dla funkcji opóźniających jest konieczność wykonania szeregu instrukcji sprawdzających ilość cykli, co zwłaszcza dla krótkich czasów wpłynie negatywnie na dokładność opóźnień. Natomiast przy odmierzaniu np. 50us błąd będzie właściwie znikomy.
Opis rejestru DWT:
DWT składa się z następujących rejestrów:
- typedef struct
- {
- __IO uint32_t CTRL; /*!< Offset: 0x000 (R/W) Control Register */
- __IO uint32_t CYCCNT; /*!< Offset: 0x004 (R/W) Cycle Count Register */
- __IO uint32_t CPICNT; /*!< Offset: 0x008 (R/W) CPI Count Register */
- __IO uint32_t EXCCNT; /*!< Offset: 0x00C (R/W) Exception Overhead Count Register */
- __IO uint32_t SLEEPCNT; /*!< Offset: 0x010 (R/W) Sleep Count Register */
- __IO uint32_t LSUCNT; /*!< Offset: 0x014 (R/W) LSU Count Register */
- __IO uint32_t FOLDCNT; /*!< Offset: 0x018 (R/W) Folded-instruction Count Register */
- __I uint32_t PCSR; /*!< Offset: 0x01C (R/ ) Program Counter Sample Register */
- __IO uint32_t COMP0; /*!< Offset: 0x020 (R/W) Comparator Register 0 */
- __IO uint32_t MASK0; /*!< Offset: 0x024 (R/W) Mask Register 0 */
- __IO uint32_t FUNCTION0; /*!< Offset: 0x028 (R/W) Function Register 0 */
- uint32_t RESERVED0[1];
- __IO uint32_t COMP1; /*!< Offset: 0x030 (R/W) Comparator Register 1 */
- __IO uint32_t MASK1; /*!< Offset: 0x034 (R/W) Mask Register 1 */
- __IO uint32_t FUNCTION1; /*!< Offset: 0x038 (R/W) Function Register 1 */
- uint32_t RESERVED1[1];
- __IO uint32_t COMP2; /*!< Offset: 0x040 (R/W) Comparator Register 2 */
- __IO uint32_t MASK2; /*!< Offset: 0x044 (R/W) Mask Register 2 */
- __IO uint32_t FUNCTION2; /*!< Offset: 0x048 (R/W) Function Register 2 */
- uint32_t RESERVED2[1];
- __IO uint32_t COMP3; /*!< Offset: 0x050 (R/W) Comparator Register 3 */
- __IO uint32_t MASK3; /*!< Offset: 0x054 (R/W) Mask Register 3 */
- __IO uint32_t FUNCTION3; /*!< Offset: 0x058 (R/W) Function Register 3 */
- } DWT_Type;
Jak można zaobserwować w zestawieniu powyżej (pobrane z core_cm4.h), wszystkie rejestry są typu Read/Write. Cały blok składa się z czterech komparatorów oraz rejestrów przechowujących czasy przejść poszczególnych cykli wraz ustawieniami licznika.
Dokładny opis rejestru można znaleźć pod tym adresem.
Poniżej przedstawię opis rejestrów jakie wykorzystuje w części programu opisanej poniżej:
DEMCR - (Debug Exception and Monitor Control Register) (link) rejestr ten pozwala na włączenie układu DW poprzez wprowadzenie 1 do bitu 24.
DTW - CTRL - rejestr kontrolny, w nim ustawiam bit 0, odpowiedzialny za wlaczenie licznika CYCCNT. Jeśli nie zostanie on włączony nic się w nim nie dzieje. Po uruchomieniu należy go zainicjalizować wartością 0 w rejestrze DWT_CYCCNT.
Programowanie:
Rozpocznę od przedstawienia funkcji pozwalającej na włączenie bloku:
- uint8_t DWT_COUNTER_ENABLE(void)
- {
- uint32_t c;
- //Wlacz TRC,
- //Ustawienie bitu TRCENA
- //Wlacza takie bloki jak DWT, ITM, ETM, TPIU
- CoreDebug->DEMCR &= ~0x01000000;
- CoreDebug->DEMCR |= 0x01000000;
- //Wlacz DWT w rejestrze kontrolnym
- DWT->CTRL &= ~0x00000001; //Czyszczenie
- DWT->CTRL |= 0x00000001; //Ustawienie
- //Ustawienie licznika na wartosc 0
- DWT->CYCCNT = 0;
- //Wartosci z CYCCNT do zmiennej c
- c = DWT->CYCCNT;
- //Czekanie
- __ASM volatile ("NOP"); __ASM volatile ("NOP"); __ASM volatile ("NOP");
- //Zwraca roznice pomiedzy DWT->CYCCNT a ta wartoscia kilka cykli wczesniej
- //Jesli wynosi ona 0 to licznik nie dziala
- if((DWT->CYCCNT - c) == 0)
- { return 0; }
- return (DWT->CYCCNT - c);
- }
Za pomocą tego licznika można stworzyć dosyć dokładną funkcję opóźniającą:
- static inline void delayUS_DWT(uint32_t us)
- {
- volatile uint32_t base = (SystemCoreClock/1000000) //1 us
- volatile uint32_t c = base*us; //Ilosc cykli do przejscia
- volatile uint32_t s = DWT->CYCCNT; //aktualna wartosc danych w rejestrze, startowa
- while((DWT->CYCCNT - s) < c)
- {;}
- }
Dobrze taką funkcję zdefiniować jako static inline, dzięki czemu można uzyskać szybsze jej wykonywanie.
Nie zaszkodzi także wyczyścić rejestru CYCCNT. W tym celu należy do niego wprowadzić wartość 0 (DWT->CYCCNT = 0;).
Poniżej przedstawię przykład pozwalający na pomiar czasu podczas którego mikrokontroler jest w trybie uśpienia oraz w trybie aktywnym:
- typedef struct {
- uint32_t Working_Cycles;
- uint32_t Sleep_Cycles;
- float Load_Percent;
- } CPULOAD_t;
- //Inicjalizacja struktury
- uint8_t STRUCT_ENABLE_CPU(CPULOAD_t* CPU_Load)
- {
- CPU_Load->Load_Percent = 0;
- CPU_Load->Sleep_Cycles = 0;
- CPU_Load->Working_Cycles = 0;
- /* Return DWT counter enabled status */
- return DWT_COUNTER_ENABLE();
- }
Poniżej funkcja pozwalająca na obliczenie czasu:
- uint8_t state_flag = 0;
- uint8_t CPULOAD_SLEEP_MODE(CPULOAD_t* CPU_Load)
- {
- uint32_t t;
- static uint32_t l = 0;
- static uint32_t WorkTim = 0;
- static uint32_t SleepTim = 0;
- //Zsumowanie czasu pracy
- WorkTim += DWT->CYCCNT - l;
- //Zapisanie czasu
- t = DWT->CYCCNT;
- //Wylaczenie przerwan
- __disable_irq();
- //Wejscie do trybu uspienia
- __WFI();
- //Zwiekszenie liczby cykli
- SleepTim += DWT->CYCCNT - t;
- //Zapisanie aktualnego czasu do zmiennej
- l = DWT->CYCCNT;
- //Ponowne wlaczenie przerwania
- __enable_irq();
- state_flag = 0;
- if ((SleepTim + WorkTim) >= SystemCoreClock)
- {
- //Wprowadzenie danych do struktury
- CPU_Load->Sleep_Cycles = SleepTim;
- CPU_Load->Working_Cycles = WorkTim;
- CPU_Load->Load_Percent = (((float)WorkTim / (float)(SleepTim + WorkTim)) * 100);
- //Resetuj czas
- SleepTim = 0;
- WorkTim = 0;
- state_flag = 1;
- }
- return state_flag;
- }