ISIX-RTOS – przykład 3 – obsługa przerwań na przykładzie zegara RTC z magistralą I2C
Kontroler I2C działa na zasadzie zdarzeń, zatem procedura obsługi przerwania sprowadza się do odczytania zdarzenia, a następnie wykonania określonej akcji przypisanej dla tego zdarzenia. W przypadku transmisji danych do układu podłączonego do magistrali I2C, po wysłaniu sekwencji start otrzymujemy zdarzenie informującej o przejęciu arbitrażu nad magistralą I2C przez kontroler. W wyniku wystąpienia zdarzenia I2C_EVENT_MASTER_MODE_SELECTED wywołujemy funkcjesend_7bit_addr(), co powoduje przesłanie adresu sprzętowego dla urządzenia. W wyniku wysłania adresu sprzętowego dostajemy zdarzenie I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED, po którym możemy rozpocząć przesyłać dane. Wraz z każdym przesłanym bajtem otrzymujemy zdarzenie I2C_EVENT_MASTER_BYTE_TRANSMITTED. Oba zdarzenia obsługiwane są przez ten sam fragment programu, którego zadaniem jest wysłanie danych z bufora. W przypadku, przesłania ostatniego bajtu, jeżeli nie ma konieczności odbierania danych, jest wywoływana metoda powodująca wygenerowanie sekwencji stop, następnie jest podnoszony semafor sem_irq informujący wątek o zakończeniu obsługi przerwania. Realizacja podnoszenia semafora odbywa się za pomocą metody sem_signal_isr(), która jest przeznaczona do wywołania z procedur obsługi przerwań. Jeżeli po zakończeniu nadawania konieczne jest odbieranie danych z urządzenia, generowany jest ponownie bit startu oraz przesyłany jest adres sprzętowy, z najmłodszym bitem ustawionym do odczytu. W wyniku przesłania adresu sprzętowego otrzymujemy zdarzenie I2C_EVENT_MASTER_RECEIVER_MODE_SELECTED, w którym sprawdzamy, czy mamy do odebrania jeden bajt. Jeżeli tak jest, to wyłączamy generowanie bitu ACK, a następnie przechodzimy do odbioru kolejnych danych. W przypadku odebrania bajtu otrzymujemy zdarzenie I2C_EVENT_MASTER_BYTE_RECEIVED, gdzie realizujemy przepisywanie bajtów do bufora odbiorczego. Gdy skończymy odbieranie przedostatniego bajtu zgodnie ze specyfikacją I2C wyłączamy generowanie bitu potwierdzenia ACK, a po odebraniu ostatniego bajtu wysyłamy polecenie wygenerowania sekwencji stop oraz podnosimy semafor sem_isr informujący o zakończeniu procedury odbioru. Po wygenerowaniu sekwencji stop magistrala I2C zostaje zwolniona.
Klasa sterownika i2c wykorzystywana jest przez klasęrtc_reader, której zadaniem jest odczytanie bieżącej godziny z zegara RTC oraz przygotowanie i przesłanie komunikatów do obiektu serwera wyświetlacza. Działanie wątku odpowiedzialnego za to zadanie realizowane jest przez metodę wirtualną pokazaną na list. 7.
List. 7. Metoda wirtualna przygotowująca i przesyłająca komunikaty do obiektu serwera wyświetlacza
//Main rtc reader core task void rtc_reader::main() { static const uint8_t pgm_regs[] = { 0x01, //Sec 0x02, //Min 0x03, //Hour 0x04, //Day num 0x05, //day 0x06, //Month 0x07, //Year 0x00 //Config }; //Software address static const uint8_t sw_addr = 0; static uint8_t buf[3]; static time_msg tmsg( 3,2 ); int status; //Send configuration registgers i2c_bus.i2c_transfer_7bit( I2C_RTC_ADDR, pgm_regs, sizeof( pgm_regs), NULL, 0); //Main task loop for(;;) { //Send configuration registgers status = i2c_bus.i2c_transfer_7bit( I2C_RTC_ADDR, &sw_addr, sizeof(sw_addr), buf, sizeof(buf) ); if(status>=0) { //If no error display time tmsg.set_time( buf[2]&0x3f, buf[1]&0x7f, buf[0]&0x7f ); } else { //If error display it tmsg.set_text("I2C ERR"); } //Send message to the i2c device disp_srv.send_message(tmsg); //Refresh screen delay isix::isix_wait(200); } }
Pierwszą czynnością jest ustawienie wartości początkowej daty i czasu. Wartości początkowe zdefiniowane są w tablicy pgm_regs. W rzeczywistej aplikacji należało by zadbać o możliwość odczytania danych z interfejsu użytkownika. Przesłanie wartości początkowych realizowane jest za pomocą funkcji i2c_transfer_7bit. Następnie wchodzimy do pętli głównej programu, gdzie realizowany jest odczyt bieżącej godziny z zegara RTC. Zgodnie z dokumentacją układu najpierw należy zapisać adres pierwszego rejestru do odczytu, następnie należy przesłać ponownie adres sprzętowy z ustawionym bitem read, a następnie odczytać rejestry zegara. Cała procedura odbywa się poprzez wywołanie pojedynczej metody i2c_transfer7bit(). Po odczytaniu danych z magistrali I2C sprawdzany jest status błędu. W przypadku wystąpienia błędu wysyłany jest komunikat tekstowy. Jeżeli odczyt danych z magistrali I2C wykonano pomyślnie, jest tworzony komunikat klasy time_msg, zawierający informację o czasie w formie tekstowej, który następnie przesyłany jest do serwera wyświetlania. Klasa time_msg dziedziczy z klasy text_msg, zatem może być wysłana bezpośrednio do serwera wyświetlania:
List. 8.
class time_msg : public text_msg { public: time_msg( short xpos=0, short ypos=0) :text_msg("",xpos,ypos) { } //Set text void set_time( short h, short m, short s) { conv_hex(sbuf,h,2); sbuf[2] = ':'; conv_hex( &sbuf[3],m,2); sbuf[5] = ':'; conv_hex( &sbuf[6],s,2); set_text( sbuf); } private: void strrev( char *str, int len); const char* conv_hex( char *txt, unsigned value, int zeros); private: char sbuf[9]; };
Utworzenie komunikatu tekstowego zawierającego aktualny czas odbywa się przez wywołanie metody set_time(), która jako argumenty przyjmuje aktualną godzinę minutę i sekundę w formacie BCD. W wyniku wykonania tej metody powstaje komunikat tekstowy, który jest wykorzystywany przez serwer wyświetlania.