Digilent Pmod i STM32 (cz. 8) – PmodMTDS, PmodRTCC i PmodCMPS2
PmodRTCC
Drugi z prezentowanych w tej części modułów to PmodRTCC zawierający zegar czasu rzeczywistego z kalendarzem oparty o układ Microchip MCP79410. Układ ten umożliwia dodatkowo ustawienie dwóch alarmów, generację fali prostokątnej, a także korzystanie z pamięci EEPROM (128 B) i SRAM (64 B). Do obsługi modułu PmodRTCC została udostępniona biblioteka RTCCI2C dostępna na stronie producenta: https://reference.digilentinc.com/pmod/pmod/rtcc/example_code. Jest ona przeznaczona dla środowiska MPIDE, dlatego na potrzeby opisywanego przykładu musiała ona zostać modyfikowana tak, aby mogła być użyta w środowisku Atollic z mikrokontrolerem STM32L496ZGT6.
Połączenie z modułem KAmeleon
Moduł PmodRTCC posiada 8-pinowe złącze dla interfejsu I2C (J2) i 2-pinowe złącze J1 zawierające piny MFP (Multi-Function Pin), a także GND. Pin MFP może pełnić różne funkcje, zależnie od konfiguracji układu – w prezentowanym przykładzie będzie on źródłem przerwań wywołanych wystąpieniem alarmu. Sygnały modułu PmodRTCC podłączono do złącza oznaczonego jako ARDUINO CONNECTOR na płytce KAmeleon zgodnie z tabelą 4 i fotografią 5.
Tabela 4. Sposób podłączenia modułu PmodRTCC do złącza ARDUINO CONNECTOR zestawu KAmeleon
Sygnał | Numer pinu PmodRTCC | Numer pinu KAmeleon ARDUINO CONNECTOR | Pin mikrokontrolera |
SCL | 1 (J2) | D15 | PF14 |
SDA | 2 (J2) | D14 | PF15 |
MFP | 1 (J1) | D13 | PB10 |
Fotografia 5. Moduł PmodRTCC podłączony do płytki KAmeleon
Kod przykładu
Za obsługę interfejsu I2C odpowiadają trzy zmodyfikowane metody klasy RTCCI2C: begin, readValue i writeValue. Pierwsza z nich, przedstawiona na listingu 5, odpowiada za konfigurację interfejsu I2C4, a także pinu PB10 do obsługi przerwania. Przerwanie jest wykrywane na zboczu opadającym sygnału MFP w momencie wystąpienia alarmu. Dla komunikacji I2C konfigurowane są dwa piny – PF14 i PF15 oraz interfejs I2C4. Dla tego ostatniego konieczne jest podanie wartości rejestru TIMINGR, przedstawionego na rysunku 6. Jest on odpowiedzialny za generację odpowiednich przebiegów czasowych na liniach SDA i SCL: SCLL i SCLH definiują długość stanu niskiego i wysokiego sygnału SCL, SCLDEL oznacza opóźnienie pomiędzy ustawieniem wartości na linii danych a zboczem narastającym sygnału zegarowego, natomiast SDADEL opóźnienie pomiędzy zboczem opadającym zegara, a zmiana stanu na linii danych. Pole PRESC wyznacza dzielnik sygnału zegarowego taktującego układ I2C mikrokontrolera.
Listing 5. Konfiguracja interfejsu I2C do komunikacji z modułem PmodRTCC
void RTCCI2C::begin() { __HAL_RCC_I2C4_CLK_ENABLE(); __HAL_RCC_GPIOB_CLK_ENABLE(); __HAL_RCC_GPIOF_CLK_ENABLE(); GPIO_InitTypeDef GPIO_InitStruct; GPIO_InitStruct.Mode = GPIO_MODE_IT_FALLING; GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH; GPIO_InitStruct.Pull = GPIO_PULLUP; GPIO_InitStruct.Pin = GPIO_PIN_10; HAL_GPIO_Init(GPIOB, &GPIO_InitStruct); HAL_NVIC_SetPriority(EXTI15_10_IRQn, 2, 0); HAL_NVIC_EnableIRQ(EXTI15_10_IRQn); GPIO_InitStruct.Mode = GPIO_MODE_AF_OD; GPIO_InitStruct.Pull = GPIO_NOPULL; GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH; GPIO_InitStruct.Alternate = GPIO_AF4_I2C4; GPIO_InitStruct.Pin = GPIO_PIN_14 | GPIO_PIN_15; HAL_GPIO_Init(GPIOF, &GPIO_InitStruct); this->i2c.Instance = I2C4; this->i2c.Init.AddressingMode = I2C_ADDRESSINGMODE_7BIT; this->i2c.Init.DualAddressMode = I2C_DUALADDRESS_DISABLE; this->i2c.Init.GeneralCallMode = I2C_GENERALCALL_DISABLE; this->i2c.Init.NoStretchMode = I2C_NOSTRETCH_DISABLE; this->i2c.Init.OwnAddress1 = 0x01; this->i2c.Init.Timing = 0x10563046; HAL_I2C_Init(&this->i2c); }
Rysunek 6. Definicja rejestru I2C_TIMINGR (źródło: dokumentacja mikrokontrolera STM32L496ZG)
Funkcje odpowiedzialne za komunikację z modułem PmodRTCC przedstawiono na listingu 6. Wykorzystują one blokujące wywołania HAL_I2C_Master_Transmit i HAL_I2C_Master_Receive biblioteki STM32Cube.
Listing 6. Funkcje realizujące odczyt i zapis rejestru.
{ uint8_t value = 0; HAL_I2C_Master_Transmit(&this->i2c, RTCC_I2C_ADDR, &address, 1, 100); HAL_I2C_Master_Receive(&this->i2c, RTCC_I2C_ADDR, &value, 1, 100); return value; } void RTCCI2C::writeValue(uint8_t address, uint8_t value) { uint8_t data[2] = {address, value}; HAL_I2C_Master_Transmit(&this->i2c, RTCC_I2C_ADDR, data, 2, 100); }
Konfiguracja rejestrów MCP79410
Oprócz wymienionych funkcji, klasa RTCCI2C zawiera także metody konfigurujące poszczególne rejestry układu MCP79410. W tabeli 5 wymieniono funkcje użyte w kodzie przykładu. Wszystkie wartości są zapisywane i odczytywane w kodzie BCD.
Tabela 5. Metody klasy RTCCI2C użyte w przykładzie
Metoda klasy RTCCI2C | Opis |
startClock/stopClock | Uruchomienie/zatrzymanie zegara przez włączenie/wyłączenie oscylatora. |
setSec/getSec | Zapis/odczyt sekund zegara lub alarmu. |
setMin/getMin | Zapis/odczyt minut zegara lub alarmu. |
setHour/getHour | Zapis/odczyt godziny zegara lub alarmu. |
setDay/getDay | Zapis/odczyt dnia tygodnia (0x01 – 0x07) zegara lub alarmu. |
setDate/getDate | Zapis/odczyt dnia miesiąca (0x01 – 0x32) zegara lub alarmu. |
setMonth/getMonth | Zapis/odczyt miesiąca (0x01 – 0x12) zegara lub alarmu. |
setYear/getYear | Zapis/odczyt roku (0x00 – 0x99) zegara lub alarmu. |
enableAlarm | Włączenie alarmu |
getAmPm | Czas dla zegara 12-godzinnego (AM/PM). |
Funkcja main
Funkcję main przykładowej aplikacji przedstawiono na listingu 7. Konfiguruje ona moduł PmodRTCC i ustawia datę: Niedziela, 4. marca 2018 23:15:30. Następnie ustawiany jest alarm na 10 sekund od zdefiniowanej daty. Pętla główna, za pomocą funkcji pomocniczej printTime, co sekundę odczytuje datę i godzinę. Odczytane wartości są od razu wysyłane na port szeregowy LPUART1. Alarm jest sygnalizowany przez zapalenie diody LED1 podłączonej do pinu PC7. Port szeregowy LPUART1 jest konfigurowany i obsługiwany za pomocą kodu znajdującego się w pliku serial.c, natomiast sterownik diody został umieszczony w pliku led.c.
Listing 7. Kod główny przykładowej aplikacji
int main(void) { HAL_Init(); SystemClock_Config(); Serial_Config(); Led_Config(); RTCCI2C myRTCC; myRTCC.begin(); //set the real time clock myRTCC.stopClock(); myRTCC.setSec(RTCC_RTCC, 0x30); myRTCC.setMin(RTCC_RTCC, 0x15); myRTCC.setHour(RTCC_RTCC, 0x11, RTCC_PM); myRTCC.setDay(RTCC_RTCC, 0x07); myRTCC.setDate(RTCC_RTCC, 0x04); myRTCC.setMonth(RTCC_RTCC, 0x03); myRTCC.setYear(0x18); // Set the alarm for 10 seconds after written time. myRTCC.setSec(RTCC_ALM0, 0x40); myRTCC.setMin(RTCC_ALM0, 0x15); myRTCC.setHour(RTCC_ALM0, 0x11, RTCC_PM); myRTCC.setDay(RTCC_ALM0, 0x07); myRTCC.setDate(RTCC_ALM0, 0x04); myRTCC.setMonth(RTCC_ALM0, 0x03); myRTCC.enableAlarm(RTCC_ALM0, RTCC_ALMC2 | RTCC_ALMC1 | RTCC_ALMC0); myRTCC.startClock(); while(1) { HAL_Delay(1000); printTime(&myRTCC, RTCC_RTCC); } }