Пример 5.5

Переключение двух задач по таймеру. Исходные тексты расположены в двух файлах:

  • example5-5.c — основная программа на языке Си.
  • timer-interrupt.S — функция обработки прерывания на ассемблере. Сохраняет и восстанавливает контекст выполнения в стеке. Для управления таймером и переключения задач вызывает функцию на языке Си.

example5-5.c

#include <p32xxxx.h>
 
//
// Размер стека для задач: пятьсот слов, или примерно два килобайта.
//
#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 <p32xxxx.h>
 
        .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
 
proj/mips/lab5-example55.txt · Последние изменения: 2012/05/08 19:46
 
Copyright (C) 1996-2013 Serge Vakulenko
serge@vak.ru