Přerušení
Přerušení je způsob, jak může hardware říct procesoru: "teď mě prosím obsluž".
Bez přerušení by program musel pořád dokola kontrolovat, jestli se něco nestalo. Tomu se říká polling.
while (1) {
if (uart_prijal_byte()) {
zpracuj_byte();
}
}
S přerušením může procesor dělat něco jiného a hardware ho vyruší až ve chvíli, kdy je opravdu potřeba.
ISR
Kód,
který se spustí při přerušení,
se jmenuje interrupt service routine,
zkráceně ISR.
V AVR kódu vypadá třeba takto:
ISR(USART0_RXC_vect)
{
const uint8_t byte = USART0.RXDATAL;
}
USART0_RXC_vect je vektor přerušení.
Říká,
která hardwarová událost tuto funkci spouští.
Tady jde o příjem bajtu přes USART0.
Povolení konkrétního přerušení
Nestačí napsat ISR.
Musíme také říct periferii,
že má přerušení vyvolávat.
Povolení přerušení příjmu přes UART může vypadat takto:
USART0.CTRLA = USART_RXCIE_bm;
USART_RXCIE_bm je bitová maska.
Zapíná přerušení při přijetí znaku.
Názorně:
V tomto konkrétním zápisu se do registru zapisuje jen jedna volba. Kdybychom chtěli přidat bit bez ovlivnění ostatních, psali bychom:
USART0.CTRLA |= USART_RXCIE_bm;
Podrobné vysvětlení |=, &= a masek je v kapitole
Ovládání registrů.
Globální povolení přerušení
Mikrokontrolér má ještě hlavní vypínač přerušení. Na AVR se zapíná funkcí:
sei();
V hlavním programu bývá pořadí podobné.
Celkový tvar main je podrobněji popsaný v kapitole
Struktura embedded programu:
int main(void)
{
clock_init();
uart_init();
communication_init();
sensors_init();
sei();
while (1) {
communication_process_rx();
communication_task();
}
}
Po sei() už mohou povolená přerušení opravdu běžet.
Před tím si program připraví hodiny,
UART,
komunikaci
a senzory.
To je důležité: nechceme, aby přerušení přišlo dřív, než jsou připravené proměnné a periferie, které ISR používá.
UART často používá přijímací buffer. Když přijde bajt, spustí se přerušení:
ISR(USART0_RXC_vect)
{
const uint8_t byte = USART0.RXDATAL;
const uint8_t next_head = (uint8_t)((uart_rx_head + 1U) % UART_RX_BUFFER_SIZE);
if (next_head == uart_rx_tail) {
uart_rx_overflow = 1;
return;
}
uart_rx_buffer[uart_rx_head] = byte;
uart_rx_head = next_head;
}
Tady ISR udělá jen rychlou práci:
- přečte přijatý bajt z
USART0.RXDATAL, - spočítá další místo v kruhovém bufferu,
- když je buffer plný, nastaví příznak přetečení,
- jinak bajt uloží.
Hlavní program pak může data z bufferu číst později. To je dobrý styl: v přerušení udělat minimum a delší práci nechat na hlavní smyčce.
Další časté přerušení je od ADC převodníku:
ADC0.INTCTRL = ADC_RESRDY_bm;
Tento řádek povolí přerušení, když je výsledek ADC měření připravený.
Obsluha vypadá zjednodušeně takto:
ISR(ADC0_RESRDY_vect)
{
sensors_accumulator[sensor_current_channel] += (int32_t)ADC0.RESULT;
++sensor_current_channel;
if (sensor_current_channel >= SENSORS_CHANNEL_COUNT) {
sensor_current_channel = 0;
++sensor_sample_index;
}
sensors_start_conversion(sensor_current_channel);
}
ADC tedy nemusí program pořád kontrolovat otázkou: "už jsi doměřil?"
ADC samo zavolá přerušení, jakmile má výsledek.
Sdílené proměnné a volatile
volatileProměnné,
které mění přerušení
a zároveň je čte hlavní program,
musí být označené jako volatile.
Typické sdílené proměnné pro UART buffer mohou vypadat takto:
static volatile uint8_t uart_rx_head;
static volatile uint8_t uart_rx_tail;
static volatile uint8_t uart_rx_overflow;
volatile říká překladači:
"tato hodnota se může změnit i mimo běžný tok programu".
Bez volatile by si překladač mohl myslet,
že se hodnota sama od sebe nemění,
a udělat optimalizaci,
která by v programu způsobila těžko hledatelnou chybu.
Kritická sekce
Někdy hlavní program potřebuje přečíst více hodnot, které může ISR zrovna měnit. Pak je potřeba přerušení na chvíli vypnout.
Kritická sekce může vypadat například takto:
const uint8_t sreg = SREG;
cli();
snapshot->values_sequence = sensors_values_sequence;
snapshot->pair_sequence = sensors_pair_sequence;
SREG = sreg;
cli() vypne globální přerušení.
SREG = sreg vrátí předchozí stav registru SREG,
tedy i původní nastavení přerušení.
Takové místo se jmenuje kritická sekce. Má být co nejkratší. Když přerušení vypneme na dlouho, hardware může mezitím čekat a některé události se mohou ztratit.
Do obsluhy přerušení nepatří dlouhé čekání. Špatný nápad je třeba:
ISR(USART0_RXC_vect)
{
_delay_ms(100);
}
Když je procesor uvnitř ISR, hlavní program stojí. Často stojí i další přerušení.
V ISR je dobré:
- přečíst data,
- uložit je do bufferu,
- nastavit příznak,
- rychle skončit.
Delší výpočty, posílání odpovědí nebo práce s protokolem patří raději do hlavní smyčky.
Obecný postup
Když chcete použít přerušení, typicky uděláte toto:
- Nastavíte periferii.
- Povolíte konkrétní interrupt bit v registru periferie.
- Připravíte sdílené proměnné jako
volatile. - Napíšete
ISR(...). - Až nakonec zavoláte
sei().
Přerušení jsou silný nástroj. Program díky nim nemusí pořád dokola hlídat každou událost. Ale platí za to tím, že musíme pečlivě přemýšlet, co se může změnit "mezi dvěma řádky" hlavního programu.