ISIX-RTOS – przykład 3 – obsługa przerwań na przykładzie zegara RTC z magistralą I2C
Opis systemu ISIX-RTOS i jego funkcji opublikowaliśmy w artykule „Mini system operacyjny dla STM32 – wprowadzenie”, który można przeczytać tu. |
Wątki mogą komunikować się ze sobą za pomocą semaforów lub kolejek komunikatów. Korzystanie z nich może powodować usypianie procesu (sleep state) w wyniku oczekiwania na pozyskanie zasobu. W przypadku przerwań uśpienie procedury obsługi przerwania nie jest możliwe z uwagi, że przerwania nie są wykonywane w kontekście procesu W związku z tym dla procedur obsługi przerwań należy wywołać tylko metody nieblokujące. W systemie ISIX, w kontekście obsługi przerwań mogą być wywoływane jedynie metody z sufiksem _isr. Sposób obsługi komunikacji pomiędzy zadaniami, a procedurami obsługi przerwań pokażemy na przykładzie obsługi magistrali I2C z podłączonym scalonym zegarem RTC M41T56C64 (na przykład z modułu KAmodRTC). Działanie aplikacji sprowadzać się będzie do odczytania godziny z zegara RTC, przetworzenia go na komunikat, oraz przesłanie go do serwera wyświetlania, zaprezentowanego w poprzednim przykładzie. Kolejny niezależny wątek podobnie jak w poprzednim przykładzie będzie migał diodą LED D1 zamontowaną w zestawie STM32Butterfly.
Uwaga! W prezentowanym przykładzie można zastosować moduły z serii KAmod: KAmodLCD1 oraz KAmodRTC dostępne na stronie http://www.kamami.pl/. |
W przykładzie zastosowano także wyświetlacz od Nokii3310 (KAmodLCD1). Sposób dołączenia wyświetlacza i układu RTC do mikrokontrolera pokazano na rys. 1.
Rys. 1. Sposób dołączenia zegara RTC i wyświetlacza LCD
Układ M41T56C64 firmy ST jest bardzo interesującym opracowaniem, integrującym w jednej obudowie zegar czasu rzeczywistego RTC z wbudowanym rezonatorem kwarcowym 32768 Hz oraz pamięć EEPROM AT24C64. Układ charakteryzuje się poborem prądu rzędu 400 nA, co umożliwia mu pracę z małej baterii litowej nawet 10 lat. Dzięki zintegrowaniu w układzie rezonatora kwarcowego nie musimy dołączać z zewnątrz praktycznie żadnych elementów, dodatkowo rezonator ten jest kalibrowany w procesie produkcji przez firmę ST, która gwarantuje dokładność rzędu +/- 5 ppm. Układ pamięci oraz zegara zostały wewnętrznie dołączone do wspólnej magistrali I2C, a rozróżnienie ich następuje na podstawie adresów sprzętowych I2C (szczegóły w dokumentacji).
Uwaga! Ustawienie zwór adresowych J1…J3 na rys. 1 ma wpływ tylko na adres pamięci EEPROM zintegrowanej w układzie M41T56C64, adres zegara RTC jest niezmienny. |
Przykładowy program pokazuje na wyświetlaczu LCD aktualną godzinę oraz miga diodą LED D1 znajdującą się na płytce STM32Butterfly. Sposób działania aplikacji z podziałem na wątki przedstawiono na rys. 2.
Rys. 2. Podział aplikacji na wątki
Wątek obsługi LED pracuje zupełnie niezależnie od pozostałych wątków, co pozwala pokazać ich wzajemną niezależność. Wątek obsługi LCD, który z punktu widzenia pozostałych wątków jest serwerem wyświetlania, odpowiada za odbiór rozkazów oraz odpowiednie sterowanie wyświetlaczem. Wątek RTC jest odpowiedzialny za odczytanie aktualnej godziny z zegara RTC poprzez interfejs I2C, sformatowanie tekstu, następnie wygenerowanie i przesłanie komunikatu dla serwera wyświetlania. Aplikacja została napisana w sposób obiektowy w języku C++, gdzie hierarchię klas przedstawiono na rys. 3.
Rys. 3. Hierarchia klas projektu
Hierarchia klas jest bardzo podobna do przykładu 2, ponieważ wykorzystano opisaną w nim architekturę serwera wyświetlania. Klasa the_application jest klasą aplikacji, w której zawarto wszystkie pozostałe obiekty. Klasa led_blink jest odpowiedzialna za cykliczne miganie diodą LED i jest dziedziczona z klasy isix::task_base implementującej obsługę wątków. Klasa display_server jest odpowiedzialna za odbiór komunikatów z kolejki FIFO oraz fizyczne sterowanie kontrolerem wyświetlacza za pomocą klasy nokia_display. Klasa i2c_host jest uniwersalną klasą sterownika, implementująca obsługę magistrali I2C z wykorzystaniem sprzętowego kontrolera I2C1 w trybie 7-bitowym. Została ona napisana w taki sposób aby można było ją rozwinąć o obsługę dodatkowych sprzętowych kontrolerów I2C, występujących w mikrokontrolerach rodziny STM32Fxxx. Deklaracja klasy znajduje się w pliku i2c_host.hpp (list. 1).
List. 1. Deklaracja klasy sterownika i2c_host
//I2c host class class i2c_host { //Friend interrupt class friend void i2c1_ev_isr_vector(void); public: enum errno { ERR_OK = 0, //All is ok ERR_BUS = -5000, //Bus error ERR_ARBITRATION_LOST = -5001, ERR_ACK_FAILURE = -5002, ERR_OVERRUN = - 5003, ERR_PEC = - 5004, //Parity check error ERR_BUS_TIMEOUT = -5005, //Bus timeout ERR_TIMEOUT = - 5006, //timeout error ERR_UNKNOWN = - 5007 }; //Default constructor i2c_host(I2C_TypeDef * const _i2c, unsigned clk_speed=100000); //I2c transfer main function int i2c_transfer_7bit( uint8_t addr, const void* wbuffer, short wsize, void* rbuffer, short rsize); private: //Interrupt service routine void isr(); //Configuration data static const unsigned TRANSFER_TIMEOUT = 1000; static const unsigned IRQ_PRIO = 1; static const unsigned IRQ_SUB = 7; //Rest of the data static const unsigned CR1_ACK_BIT = 0x0400; static const unsigned CR1_START_BIT = 0x0100; static const unsigned CR1_STOP_BIT = 0x0200; static const uint16_t I2C_IT_BUF = 0x0400; static const uint16_t I2C_IT_EVT = 0x0200; static const uint16_t I2C_IT_ERR = 0x0100; static const uint16_t CR1_PE_SET = 0x0001; //Get last i2c event uint32_t get_last_event() { static const uint32_t sflag_mask = 0x00FFFFFF; return ( static_cast( i2c->SR1) | static_cast ( i2c->SR2)<<16 ) & sflag_mask; } //Send 7 bit address on the i2c bus void send_7bit_addr(uint8_t addr) { i2c->DR = addr; } //Send data on the bus void send_data(uint8_t data) { i2c->DR = data; } //Read data from the bus uint8_t receive_data() { return i2c->DR; } //CR1 reg enable disable void cr1_reg(unsigned bit, bool en) { if(en) i2c->CR1 |= bit; else i2c->CR1 &= ~bit; } //ACK ON control void ack_on(bool on) { cr1_reg( CR1_ACK_BIT, on ); } //Generate start void generate_start(bool en=true) { cr1_reg( CR1_START_BIT, en ); } //Generate stop void generate_stop(bool en=true) { cr1_reg( CR1_STOP_BIT, en ); } //Clear data flags (dummy read) void clear_flags() { static_cast ( static_cast ( i2c->SR1)); static_cast ( static_cast ( i2c->DR)); } //Control enabling disabling int in the device void devirq_on( bool en=true) { if(en) /* Enable I2C interrupt */ i2c->CR2 |= I2C_IT_EVT| I2C_IT_ERR; else /* diasable I2C interrupt */ i2c->CR2 &= ~(I2C_IT_EVT | I2C_IT_ERR); } //Set bus speed void set_speed(unsigned speed); //Translate error to the error code int get_hwerror(); private: //Data //I2c device number I2C_TypeDef *i2c; //Tx buffer pointer const uint8_t *tx_buf; //Rx buffer pointer uint8_t *rx_buf; //Busy semaphore isix::semaphore sem_lock; //Read semaphore isix::semaphore sem_irq; //Bus address volatile uint8_t bus_addr; //Bus error flags volatile uint8_t err_flag; //Tx counter volatile short tx_bytes; //Rx counter volatile short rx_bytes; //Position in the buffer volatile short buf_pos; private: //Noncopyable i2c_host( i2c_host &); i2c_host& operator = ( const i2c_host&); };
Klasa została zaprzyjaźniona z funkcją i2c1_ev_isr_vector stanowiącą wektor obsługi przerwania od kontrolera I2C1. Wszystkie procedury obsługi przerwań muszą mieć linkowanie typu C (extern „C”). Funkcje wywoływane są w momencie wystąpienia przerwania bez żadnych dodatkowych parametrów, co wymusza istnienie dostępu do instancji klasy kontrolera I2C poprzez wskaźnik lub referencję globalną. Wskaźnik do obiektu i2c umożliwiający dostęp do obiektu kontrolera I2C przez funkcję obsługi przerwania umieszczono w nienazwanej przestrzeni nazw w pliku implementacji klasy (i2c_host.cpp), przez co dostęp do niej jest możliwy tylko w obrębie tego modułu (list. 2).
List. 2.
namespace { i2c_host *i2c1_obj; }