Ten post będzie zawierał przerobiony program z mikrokontrolera STM32F103RB, który został opublikowany w jednym z poprzednich postów.
Watchdog w STM32F4
Podobnie jak poprzednio w tym mikrokontrolerze występują dwa rodzaje układów czuwających, niezależny i okienkowy.
Pierwszy z nich taktowany jest z LSI, którego częstotliwość oscyluje w okolicach 32 kHz. Co jest jego niewątpliwą zaletą, ponieważ może działać właściwie w każdym przypadku poprawnie. Dodatkowo ten sygnał może zostać podzielony przez wartości będące potęgą dwójki, maksymalnie dzielnik może wynosić 256 minimalnie 4. Wobec tego najmniejsza wartość częstotliwości może wynosić 128Hz.
Dopuszczalne wartości dzielnika, czasu oraz wartości początkowej przedstawiono w tabelce poniżej zaczerpniętej z dokumentacji firmy.
Rys. 1. Ustawienie wartości dzielnika
Drugi z nich taktowany jest poprzez szynę APB1. Jego działanie jest takie jak w przypadku mikrokontrolera zastosowanego na płytce Nucleo.
Niezależny układ czuwający programowanie
Działanie układu rozpoczyna się od wprowadzenia wartości do rejestu IWDG_KR 0x5555. Ta wartość pozwala na uzyskanie dostępu do rejestrów chronionych. IWDG_PR pozwala na ustawienie wartości częstotliwości. Natomiast IWDG_RLR daje możliwość wprowadzenia tzw. reload czyli początkową wartość licznika, która będzie decydować po jakim czasie nastąpi jego przepełnienie. Od tej wartości nastąpi zliczanie w dół.
Następnie do rejestru IWDG_KR należy wprowadzić wartość wartość 0xCCCC, która pozwoli na jego przeładowanie. Następnie do tego rejestru wprowadzona zostaje wartość 0xAAAA. Dzięki temu zostanie on włączony oraz nastąpi jego włączenie.
W celu jego zaprogramowania można także posłużyć się bezpośrednio bibliotekami API. Przez co jego inicjalizacja będzie wyglądała identycznie jak dla mikrokontrolera STM32F103RB.
Poniżej przedstawiam opis poszczególnych rejestrów wraz z dopuszczalnymi do ustawienia wartościami.
IWDG_KR - pozwala na dostęp do niezależnego układu czuwającego. Adres rejestru wynosi 0x00. Można do niego wprowadzić trzy wartości:
- 0xAAAA - ta wartość ma być wprowadzana co zaprogramowaną ilość czasu, inaczej zostanie wykonany reset mikrokontrolera.
- 0xCCCC - włącza pracę układu
- 0x5555 - pozwala na uzyskanie dostępu do dwóch rejestrów chronionych IWDG_PR oraz IWDG_RLR.
Rys. 2. Key Register
Jak można zaobserwować na rysunku 2, możliwe jest uzyskanie dostępu tylko do 15 młodszych bitów.
IWDG_PR - ten rejestr po odblokowaniu zapewnia możliwość ustawienia dzielnika częstotliwości. Jego adres wynosi 0x04.
IWDG_PR - ten rejestr po odblokowaniu zapewnia możliwość ustawienia dzielnika częstotliwości. Jego adres wynosi 0x04.
Rys. 3. Prescaler Register
W jego przypadku dostęp można uzyskać do trzech najmłodszych bitów. Dzięki nim można ustawić następujące wartości dzielnika:
IWDG_RLR - pozwala na ustawienie wartości początkowej od jakiej nastąpi zliczanie. Są to wartości zależne od ustawionego dzielnika. Ich wykaz przedstawiłem na rysunku 1. Offset rejestru wynosi 0x08.
- 000 - dzielnik wynosi 4
- 001 - dzielnik wynosi 8
- 010 - dzielnik wynosi 16
- 011 - dzielnik wynosi 32
- 100 - dzielnik wynosi 64
- 101 - dzielnik wynosi 128
- 110 - dzielnik wynosi 256
- 111 - dzielnik wynosi 256
IWDG_RLR - pozwala na ustawienie wartości początkowej od jakiej nastąpi zliczanie. Są to wartości zależne od ustawionego dzielnika. Ich wykaz przedstawiłem na rysunku 1. Offset rejestru wynosi 0x08.
Rys. 4. Reload register
IWDG_SR - zapewnia możliwość uaktualnienia wartości początkowej (RVU reset) lub wartości dzielnika (PVU reset). Adres rejestru wynosi 0x0C.
Rys. 5. Status register
Poniżej przedstawiam mapę wszystkich czterech rejestrów zestawionych razem.
Rys. 6. Mapa rejestrów niezależnego układu czuwającego.
Program
Poniżej przedstawiam fragment programu inicjujący niezależny układ czuwający. Przygotowałem go w dwóch wersjach jeden w całości wykorzystujący operacje na rejestrach, drugi natomiast bezpośrednio na bibliotekach.
Na rejestrach:
void IWDGKonfiguracjaReg(void) { //Zezwolenie na zapis do rejestrów IWDG_PR oraz IWDG_RLR IWDG->KR = 0x5555; //Ustawienie taktowania, 32kHz/32 = 1024Hz IWDG->PR = 0x03; //Ustawienie wartosci poczatkowej IWDG->RLR = 0xFF; //Przeladowanie wybranej konfiguraci IWDG->KR = 0xAAAA; //Wlaczenie watchdoga oraz sygnalu taktujacego LSI IWDG->KR = 0xCCCC; }
Na bibliotekach API:
void IWDGKonfiguracja(void) { //Zezwolenie na zapis do rejestrów IWDG_WriteAccessCmd(IWDG_WriteAccess_Enable); //Ustawienie taktowania, 32kHz/32 = 1024Hz IWDG_SetPrescaler(IWDG_Prescaler_32); //Ustawienie wartosci poczatkowej IWDG_SetReload(0xFF); //Przeladowanie wybranej konfiguraci IWDG_ReloadCounter(); //Wlaczenie watchdoga oraz sygnalu taktujacego LSI IWDG_Enable(); }
Do tego należy także włączyć piny GPIO odpowiedzialne za uruchomienie diod wbudowanych w płytkę.
void LedInit(void) { GPIO_InitTypeDef GPIO_InitDef; //Wlaczenie zegara RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOD, ENABLE); //Wybranie pinów GPIO_InitDef.GPIO_Pin = GPIO_Pin_12 | GPIO_Pin_13 | GPIO_Pin_14 | GPIO_Pin_15; //Wyjscie jako push-pull GPIO_InitDef.GPIO_OType = GPIO_OType_PP; //Ustawienie pinu jako wyjscie GPIO_InitDef.GPIO_Mode = GPIO_Mode_OUT; //Bez rezystora podciagajacego GPIO_InitDef.GPIO_PuPd = GPIO_PuPd_NOPULL; //Ustawienie czestotliwosci GPIO_InitDef.GPIO_Speed = GPIO_Speed_100MHz; //Wlaczenie lini GPIOD GPIO_Init(GPIOD, &GPIO_InitDef); } void ButtonInit(void) { GPIO_InitTypeDef GPIO_InitDef; RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA, ENABLE); GPIO_InitDef.GPIO_Pin = GPIO_Pin_0; GPIO_InitDef.GPIO_OType = GPIO_OType_PP; //Ustawienie jako wejscie GPIO_InitDef.GPIO_Mode = GPIO_Mode_IN; GPIO_InitDef.GPIO_PuPd = GPIO_PuPd_NOPULL; GPIO_InitDef.GPIO_Speed = GPIO_Speed_100MHz; GPIO_Init(GPIOA, &GPIO_InitDef); } void LedOn(uint16_t led) { GPIO_SetBits(GPIOD, led); } void LedOff(uint16_t led) { GPIO_ResetBits(GPIOD, led); }
Poniżej przedstawiam całość programu zestawionego z pozostałymi funkcjami.
#include "stm32f4xx.h" #include "stm32f4xx_iwdg.h" void LedInit(void); void IWDGKonfiguracja(void); void IWDGKonfiguracjaReg(void); void ButtonInit(void); void LedOn(uint16_t); void LedOff(uint16_t); int main() { volatile unsigned long int i; //Konfiguracja GPIO LedInit(); ButtonInit(); //Warunek sprawdzajacy czy system zaczal prace po wykorzystaniu resetu //przez uklad czuwajacy, Jezeli tak to wejscie w petle if (RCC_GetFlagStatus(RCC_FLAG_IWDGRST) != RESET) { //Wlaczenie diody wbudowanej GPIO_SetBits(GPIOD, GPIO_Pin_12); //Wyczyszczenie flagi resetu RCC_ClearFlag(); //Petla opózniajaca for (i=0;i<10000000;i++); } //Konfiguracja ukladu czuwajacego zewnetrznego IWDGKonfiguracjaReg(); //IWDGKonfiguracja(); GPIO_ResetBits(GPIOD, GPIO_Pin_12 | GPIO_Pin_13 | GPIO_Pin_14 | GPIO_Pin_15); while (1) { //Przeladowanie Watchdoga IWDG_ReloadCounter(); //W przypadku wcisniecia przycisku warunek zostaje zpelniony if (GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_0) == 0) { //Zapalenie diody GPIO_SetBits(GPIOD, GPIO_Pin_13); //Wejscie w nieskonczona petle for(;;) {} } GPIO_WriteBit(GPIOD, GPIO_Pin_14, (BitAction)(1-GPIO_ReadOutputDataBit(GPIOD, GPIO_Pin_14))); for (i=0;i<2000000;i++); }; //return 0; } void IWDGKonfiguracja(void) { //Zezwolenie na zapis do rejestrów IWDG_WriteAccessCmd(IWDG_WriteAccess_Enable); //Ustawienie taktowania, 32kHz/32 = 1024Hz IWDG_SetPrescaler(IWDG_Prescaler_32); //Ustawienie wartosci poczatkowej IWDG_SetReload(0xFF); //Przeladowanie wybranej konfiguraci IWDG_ReloadCounter(); //Wlaczenie watchdoga oraz sygnalu taktujacego LSI IWDG_Enable(); } void IWDGKonfiguracjaReg(void) { //Zezwolenie na zapis do rejestrów IWDG_PR oraz IWDG_RLR IWDG->KR = 0x5555; //Ustawienie taktowania, 32kHz/32 = 1024Hz IWDG->PR = 0x03; //Ustawienie wartosci poczatkowej IWDG->RLR = 0xFF; //Przeladowanie wybranej konfiguraci IWDG->KR = 0xAAAA; //Wlaczenie watchdoga oraz sygnalu taktujacego LSI IWDG->KR = 0xCCCC; } void LedInit(void) { GPIO_InitTypeDef GPIO_InitDef; //Wlaczenie zegara RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOD, ENABLE); //Wybranie pinów GPIO_InitDef.GPIO_Pin = GPIO_Pin_12 | GPIO_Pin_13 | GPIO_Pin_14 | GPIO_Pin_15; //Wyjscie jako push-pull GPIO_InitDef.GPIO_OType = GPIO_OType_PP; //Ustawienie pinu jako wyjscie GPIO_InitDef.GPIO_Mode = GPIO_Mode_OUT; //Bez rezystora podciagajacego GPIO_InitDef.GPIO_PuPd = GPIO_PuPd_NOPULL; //Ustawienie czestotliwosci GPIO_InitDef.GPIO_Speed = GPIO_Speed_100MHz; //Wlaczenie lini GPIOD GPIO_Init(GPIOD, &GPIO_InitDef); } void ButtonInit(void) { GPIO_InitTypeDef GPIO_InitDef; RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA, ENABLE); GPIO_InitDef.GPIO_Pin = GPIO_Pin_0; GPIO_InitDef.GPIO_OType = GPIO_OType_PP; //Ustawienie jako wejscie GPIO_InitDef.GPIO_Mode = GPIO_Mode_IN; GPIO_InitDef.GPIO_PuPd = GPIO_PuPd_NOPULL; GPIO_InitDef.GPIO_Speed = GPIO_Speed_100MHz; GPIO_Init(GPIOA, &GPIO_InitDef); } void LedOn(uint16_t led) { GPIO_SetBits(GPIOD, led); } void LedOff(uint16_t led) { GPIO_ResetBits(GPIOD, led); }