LinkedIn YouTube Facebook
Szukaj

Wstecz
Artykuły

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

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

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

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;
}