=== Пример 5.5 ===
Переключение двух задач по таймеру. Исходные тексты расположены в двух файлах:
* example5-5.c --- основная программа на языке Си.
* timer-interrupt.S --- функция обработки прерывания на ассемблере. Сохраняет и восстанавливает контекст выполнения в стеке. Для управления таймером и переключения задач вызывает функцию на языке Си.
=== example5-5.c ===
#include
//
// Размер стека для задач: пятьсот слов, или примерно два килобайта.
//
#define STACK_NWORDS 500
//
// Память для стеков задач.
//
int task1_stack [STACK_NWORDS];
int task2_stack [STACK_NWORDS];
//
// Указатели стека для задач.
//
int *task1_stack_pointer;
int *task2_stack_pointer;
//
// Номер работающей задачи.
//
int current_task = 0;
//
// Текущее время в миллисекундах.
//
volatile unsigned time_msec;
//
// Отображение одного сегмента на дисплее
//
void display (int segment, int on)
{
switch (segment) {
case 'a':
if (on) LATDSET = 1 << 8; // Контакт 2 - сигнал RD8
else LATDCLR = 1 << 8;
break;
case 'b':
if (on) LATDSET = 1 << 0; // Контакт 3 - сигнал RD0
else LATDCLR = 1 << 0;
break;
case 'c':
if (on) LATFSET = 1 << 1; // Контакт 4 - сигнал RF1
else LATFCLR = 1 << 1;
break;
case 'd':
if (on) LATDSET = 1 << 1; // Контакт 5 - сигнал RD1
else LATDCLR = 1 << 1;
break;
case 'e':
if (on) LATDSET = 1 << 2; // Контакт 6 - сигнал RD2
else LATDCLR = 1 << 2;
break;
case 'f':
if (on) LATDSET = 1 << 9; // Контакт 7 - сигнал RD9
else LATDCLR = 1 << 9;
break;
case 'g':
if (on) LATDSET = 1 << 10; // Контакт 8 - сигнал RD10
else LATDCLR = 1 << 10;
break;
case 'h':
if (on) LATDSET = 1 << 3; // Контакт 9 - сигнал RD3
else LATDCLR = 1 << 3;
break;
}
}
//
// Опрос состояния кнопки.
// Возвращает ненулевое значение, если кнопка нажата.
//
int button_pressed (int button)
{
switch (button) {
case '1':
return ~PORTG >> 8 & 1; // Контакт 11 - сигнал RG8
case '2':
return ~PORTG >> 7 & 1; // Контакт 12 - сигнал RG7
}
return 0;
}
//
// Функция ожидания, с остановом при нажатой кнопке.
//
void wait (unsigned msec, int button)
{
unsigned t0 = time_msec;
while (button_pressed (button) ||
time_msec - t0 < msec)
{
// Если нажата указанная кнопка - ждём,
// пока она не освободится.
}
}
//
// Первая задача: вращаем нижнее кольцо восьмёрки, сегменты D-E-G-C.
// Функция не должна возвращать управление.
//
void task1()
{
for (;;) {
display ('d', 1); wait (100, '1'); display ('d', 0);
display ('e', 1); wait (100, '1'); display ('e', 0);
display ('g', 1); wait (100, '1'); display ('g', 0);
display ('c', 1); wait (100, '1'); display ('c', 0);
}
}
//
// Вторая задача: вращаем верхнее кольцо восьмёрки, сегменты A-B-G-F.
// Функция не должна возвращать управление.
//
void task2()
{
for (;;) {
display ('a', 1); wait (75, '2'); display ('a', 0);
display ('b', 1); wait (75, '2'); display ('b', 0);
display ('g', 1); wait (75, '2'); display ('g', 0);
display ('f', 1); wait (75, '2'); display ('f', 0);
}
}
//
// Установка начального значения стека для запуска новой задачи.
// Возвращает начальное значение для регистра sp.
//
int *create_task (int start, int *stack)
{
// Для хранения контекста в стеке выделяется 34 слова.
stack += STACK_NWORDS - 34 - 4;
stack [3] = 0; // Регистр at
stack [4] = 0; // Регистр v0
stack [5] = 0; // Регистр v1
stack [6] = 0; // Регистр a0
stack [7] = 0; // Регистр a1
stack [8] = 0; // Регистр a2
stack [9] = 0; // Регистр a3
stack [10] = 0; // Регистр t0
stack [11] = 0; // Регистр t1
stack [12] = 0; // Регистр t2
stack [13] = 0; // Регистр t3
stack [14] = 0; // Регистр t4
stack [15] = 0; // Регистр t5
stack [16] = 0; // Регистр t6
stack [17] = 0; // Регистр t7
stack [18] = 0; // Регистр s0
stack [19] = 0; // Регистр s1
stack [20] = 0; // Регистр s2
stack [21] = 0; // Регистр s3
stack [22] = 0; // Регистр s4
stack [23] = 0; // Регистр s5
stack [24] = 0; // Регистр s6
stack [25] = 0; // Регистр s7
stack [26] = 0; // Регистр t8
stack [27] = 0; // Регистр t9
stack [28] = 0; // Регистр s8
stack [29] = 0; // Регистр ra
stack [30] = 0; // Регистр hi
stack [31] = 0; // Регистр lo
stack [32] = 0x10000003; // Регистр Status: биты CU0, EXL, IE
stack [33] = start; // Регистр EPC: адрес начала
return stack;
}
//
// Начальная инициализация.
//
int init()
{
// Задаём количество тактов ожидания для памяти.
// Скорость работы процессора увеличится.
CHECON = 2;
BMXCONCLR = 0x40;
CHECONSET = 0x30;
// Разрешаем кэширование сегмента kseg0, будет еще быстрее.
// Это задаётся в младших битах регистра Config.
int config;
asm volatile ("mfc0 %0, $16" : "=r" (config));
config |= 3;
asm volatile ("mtc0 %0, $16" : : "r" (config));
// Отключаем порт JTAG, чтобы освободить эти ножки для чего-то полезного.
DDPCON = 0;
// Переключаем все сигналы порта B в цифровой режим.
AD1PCFG = ~0;
//
// Контроллер прерываний.
//
INTCON = 0; // Interrupt Control
IPTMR = 0; // Temporal Proximity Timer
IFS0 = IFS1 = 0; // Interrupt Flag Status
IEC0 = IEC1 = 0; // Interrupt Enable Control
IPC0 = IPC1 = // Interrupt Priority Control
IPC2 = IPC3 =
IPC4 = IPC5 =
IPC6 = IPC7 =
IPC8 = IPC11 = 1<<2 | 1<<10 | 1<<18 | 1<<26;
}
//
// Основная функция программы.
//
int main()
{
init();
// Устанавливаем прерывание от таймера с частотой 1000 Гц.
int compare = F_CPU / 2 / 1000;
asm volatile ("mtc0 %0, $9" : : "r" (0));
asm volatile ("mtc0 %0, $11" : : "r" (compare));
IEC0SET = 1 << _CORE_TIMER_IRQ;
// Сигналы от кнопок используем как входы.
TRISGSET = 1 << 8; // Кнопка 1 - сигнал RG8
TRISGSET = 1 << 7; // Кнопка 2 - сигнал RG7
// Сигналы управления 7-сегментным индикатором - выходы.
TRISDCLR = 1 << 8; LATDCLR = 1 << 8; // Сегмент A - сигнал RD8
TRISDCLR = 1 << 0; LATDCLR = 1 << 0; // Сегмент B - сигнал RD0
TRISFCLR = 1 << 1; LATFCLR = 1 << 1; // Сегмент C - сигнал RF1
TRISDCLR = 1 << 1; LATDCLR = 1 << 1; // Сегмент D - сигнал RD1
TRISDCLR = 1 << 2; LATDCLR = 1 << 2; // Сегмент E - сигнал RD2
TRISDCLR = 1 << 9; LATDCLR = 1 << 9; // Сегмент F - сигнал RD9
TRISDCLR = 1 << 10; LATDCLR = 1 << 10; // Сегмент G - сигнал RD10
TRISDCLR = 1 << 3; LATDCLR = 1 << 3; // Сегмент H - сигнал RD3
// Создаём две задачи.
task1_stack_pointer = create_task ((int) task1, task1_stack);
task2_stack_pointer = create_task ((int) task2, task2_stack);
// Разрешаем прерывания.
asm volatile ("ei");
for (;;) {
// Ничего не делаем, ждём прерывания от таймера.
// После первого же прерывания начинают работать задачи.
}
}
//
// Эта функция вызывается каждую миллисекунду из обработчика
// прерывания от таймера. В качестве аргумента она получает
// указатель стека текущей задачи, и должна вернуть указатель
// стека для следующей задачи, на которую надо переключиться.
//
int *task_switch (int *sp)
{
// Увеличиваем регистр Compare чтобы получить следующее
// прерывание еще через миллисекунду.
int compare;
asm volatile ("mfc0 %0, $11" : "=r" (compare));
compare += F_CPU / 2 / 1000;
asm volatile ("mtc0 %0, $11" : : "r" (compare));
// Сбрасываем флаг в контроллере прерываний.
IFS0CLR = 1 << _CORE_TIMER_IRQ;
// Наращиваем счётчик времени.
++time_msec;
// Запоминаем значение указателя стека для текущей задачи.
if (current_task == 1) {
task1_stack_pointer = sp;
} else if (current_task == 2) {
task2_stack_pointer = sp;
}
// Переключаемся на другую задачу: меняем указатель стека.
if (current_task == 1) {
current_task = 2;
sp = task2_stack_pointer;
} else {
current_task = 1;
sp = task1_stack_pointer;
}
// Устанавливаем новое значение стека.
return sp;
}
=== timer-interrupt.S ===
#include
.text // Начинаем секцию выполняемого кода.
.set noreorder // Отключаем переупорядочивание инструкций
//
// Обработчик прерывания от таймера.
//
timer_handler:
addiu sp, sp, -136 // Выделяем в стеке 34 слова
mfc0 k0, _CP0_EPC // Сохраняем адрес возврата EPC
sw k0, 132(sp)
mfc0 k1, _CP0_STATUS // Сохраняем режим процессора Status
sw k1, 128(sp)
mflo k0
sw k0, 124(sp) // Регистр LO
mfhi k1
sw k1, 120(sp) // Регистр HI
sw ra, 116(sp) // Регистр 31
sw s8, 112(sp) // Регистр 30
sw t9, 108(sp) // Регистр 25
sw t8, 104(sp) // Регистр 24
sw s7, 100(sp) // Регистр 23
sw s6, 96(sp) // Регистр 22
sw s5, 92(sp) // Регистр 21
sw s4, 88(sp) // Регистр 20
sw s3, 84(sp) // Регистр 19
sw s2, 80(sp) // Регистр 18
sw s1, 76(sp) // Регистр 17
sw s0, 72(sp) // Регистр 16
sw t7, 68(sp) // Регистр 15
sw t6, 64(sp) // Регистр 14
sw t5, 60(sp) // Регистр 13
sw t4, 56(sp) // Регистр 12
sw t3, 52(sp) // Регистр 11
sw t2, 48(sp) // Регистр 10
sw t1, 44(sp) // Регистр 9
sw t0, 40(sp) // Регистр 8
sw a3, 36(sp) // Регистр 7
sw a2, 32(sp) // Регистр 6
sw a1, 28(sp) // Регистр 5
sw a0, 24(sp) // Регистр 4
sw v1, 20(sp) // Регистр 3
sw v0, 16(sp) // Регистр 2
sw $1, 12(sp) // Регистр 1
// Меняем значение указателя стека: переключение на другую задачу.
jal task_switch
move a0, sp
move sp, v0 // sp = task_switch (sp)
// Восстанавливаем все регистры из стека.
lw a1, 132(sp)
mtc0 a1, _CP0_EPC // Адрес возврата EPC
lw a1, 128(sp)
mtc0 a1, _CP0_STATUS // Режим процессора Status
lw a1, 124(sp)
mtlo a1 // Регистр LO
lw a1, 120(sp)
mthi a1 // Регистр HI
lw ra, 116(sp) // Регистр 31
lw s8, 112(sp) // Регистр 30
lw t9, 108(sp) // Регистр 25
lw t8, 104(sp) // Регистр 24
lw s7, 100(sp) // Регистр 23
lw s6, 96(sp) // Регистр 22
lw s5, 92(sp) // Регистр 21
lw s4, 88(sp) // Регистр 20
lw s3, 84(sp) // Регистр 19
lw s2, 80(sp) // Регистр 18
lw s1, 76(sp) // Регистр 17
lw s0, 72(sp) // Регистр 16
lw t7, 68(sp) // Регистр 15
lw t6, 64(sp) // Регистр 14
lw t5, 60(sp) // Регистр 13
lw t4, 56(sp) // Регистр 12
lw t3, 52(sp) // Регистр 11
lw t2, 48(sp) // Регистр 10
lw t1, 44(sp) // Регистр 9
lw t0, 40(sp) // Регистр 8
lw a3, 36(sp) // Регистр 7
lw a2, 32(sp) // Регистр 6
lw a1, 28(sp) // Регистр 5
lw a0, 24(sp) // Регистр 4
lw v1, 20(sp) // Регистр 3
lw v0, 16(sp) // Регистр 2
lw $1, 12(sp) // Регистр 1
addiu sp, sp, 136 // Освобождаем место в стеке
eret // Выходим из прерывания
//
// Специальная секция кода, которую загрузчик
// разместит по адресу входа в прерывание 0.
//
.section .vector_0, "ax", @progbits
timer_vector:
j timer_handler
nop