(Фрагмент курса аппаратного программирования от Юрия Панчула)

Часть 5. Программирование на голом железе и зачатие операционной системы

(сырой материал)

  1. Лаба - знакомство с платой chipKit32 / PIC32 / MIPS и программирование её на С с помощью Arduino-подобного GUI. Кнопочки, лампочки, дисплейчик, IO Shield.
  2. Введение в ассемблер на примере MIPS
  3. Лаба - загрузка в память программок на ассемблере
  4. Концепция простейшей многозадачной операционной системы
  5. Лаба - своя игрушечная многозадачная ОС, которая бутится, ставит обработку прерывания по таймеру, в котором переключает задачи по схеме round-robin.

5.1. Знакомство с платой Uno32

Лабораторная работа: знакомство с платой Uno32. Процессор PIC32 с архитектурой MIPS32. Простая среда разработки chipKit MPIDE и программирование на С. Подключение кнопочек и семисегментного индикатора с помощью макетной платы. Концепция прерывания.

Необходимое оборудование:

  • Плата Uno32
  • Адаптер PICkit 3 (начиная с раздела 5.3)
  • Компьютер с Linux или Windows
  • Кабель mini-USB
  • Универсальная макетная плата
  • Две кнопки с проводами
  • Семисегментный светодиодный индикатор
  • Девять резисторов 220 Ом с проводами

Пример 1: управление двумя светодиодами, имеющимися на плате Uno32, с помощью двух внешних кнопок.

Пример 2: управление семисегментным индикатором. Игра “нажми вместе”. При нажатии двух кнопок на индикаторе отображается разность времени нажатия в миллисекундах. Используется аппаратное прерывание от таймера.

Студентам предлагается доработать примеры в некотором направлении. Возможный список задач:

  • Сделать счетчик нажатий кнопок. Одна кнопка увеличивает счётчик, другая уменьшает.
  • Секундомер. Первая кнопка запускает отсчет. Вторая увеличивает темп в 10 или 100 раз.
  • “Электронный кубик” - генератор случайных чисел.

Пример 5.1.1.

Среда MPIDE. Управление двумя светодиодами, имеющимися на плате Uno32, с помощью двух внешних кнопок.

TODO: нарисовать схему подключения кнопок. Требуются два резистора 10кОм для подтяжки к +3.3В.

// Кнопки на контактах 11 и 12.
const int button1 = 11;
const int button2 = 12;
 
// Светодиоды на контактах 13 и 43.
const int led1 = 13;
const int led2 = 43;
 
void setup()
{
  // Сигналы от кнопок используем как входы.
  pinMode (button1, INPUT);
  pinMode (button2, INPUT);
 
  // Сигналы управления светодиодами - выходы.
  pinMode (led1, OUTPUT);
  pinMode (led2, OUTPUT);
}
 
void loop()
{
  int need_wait = 0;
 
  // Опрашиваем первую кнопку.
  if (digitalRead (button1) == HIGH) {
    // Не нажата - гасим первый светодиод.
    digitalWrite (led1, LOW);
    need_wait = 1;
  }
 
  // Опрашиваем вторую кнопку.
  if (digitalRead (button2) == HIGH) {
    // Не нажата - гасим второй светодиод.
    digitalWrite (led2, LOW);
    need_wait = 1;
  }
 
  // Если надо, подождём.
  if (need_wait)
    delay (150);
 
  // Зажигаем оба светодиода.
  digitalWrite (led1, HIGH);
  digitalWrite (led2, HIGH);
  delay (50);
}

Пример 5.1.2

Управление семисегментным индикатором. Игра “нажми вместе”. При нажатии двух кнопок на индикаторе отображается разность времени нажатия в миллисекундах. Используется аппаратное прерывание от таймера.

TODO: нарисовать примерную схему подключения 7-сегментного индикатора. Требуются восемь резисторов 220Ом для ограничения тока через светодиоды. Годится любой светодиодный индикатор с общим катодом. Можно с общим анодом, если поменять местами LATxSET и LATxCLR в функции display().

led-diagram-3.jpg

Контакт Uno32 DigitalСегмент LED
2 A
3 B
4 C
5 D
6 E
7 F
8 G
9 H

#include <plib.h>
 
// Кнопки на контактах 11 и 12.
const int button1 = 11;
const int button2 = 12;
 
// 7-сегментный индикатор на контактах 2-9.
const int segm_a = 2;
const int segm_b = 3;
const int segm_c = 4;
const int segm_d = 5;
const int segm_e = 6;
const int segm_f = 7;
const int segm_g = 8;
const int segm_h = 9;
 
// Время в миллисекундах.
unsigned msec;
 
// Состояние кнопок.
int button1_was_pressed, button2_was_pressed;
 
// Моменты нажатия кнопок.
unsigned time1, time2;
 
void setup()
{
  // Сигналы от кнопок используем как входы.
  pinMode (button1, INPUT);
  pinMode (button2, INPUT);
 
  // Сигналы управления светодиодами - выходы.
  pinMode (segm_a, OUTPUT);
  pinMode (segm_b, OUTPUT);
  pinMode (segm_c, OUTPUT);
  pinMode (segm_d, OUTPUT);
  pinMode (segm_e, OUTPUT);
  pinMode (segm_f, OUTPUT);
  pinMode (segm_g, OUTPUT);
  pinMode (segm_h, OUTPUT);
 
  // Устанавливаем прерывание от таймера с частотой 1000 Гц.
  OpenTimer2 (T2_ON | T2_PS_1_256, F_CPU / 256 / 1000);
  ConfigIntTimer2 (T2_INT_ON | T2_INT_PRIOR_3);
}
 
// Отображение одной цифры на дисплее
void display (unsigned digit)
{
  static const unsigned pattern [10] = {
    1+2+4+8+16+32,     // Цифра 0
      2+4,             // Цифра 1
    1+2  +8+16   +64,  // Цифра 2
    1+2+4+8      +64,  // Цифра 3
      2+4     +32+64,  // Цифра 4
    1  +4+8   +32+64,  // Цифра 5
    1  +4+8+16+32+64,  // Цифра 6
    1+2+4,             // Цифра 7
    1+2+4+8+16+32+64,  // Цифра 8
    1+2+4+8   +32+64,  // Цифра 9
  };
 
  if (digit > 9)
    digit = 9;
  unsigned mask = pattern[digit];
 
  if (mask & 1)   LATDSET = 1 << 8;  // Контакт 2 - сигнал RD8
  else            LATDCLR = 1 << 8;
  if (mask & 2)   LATDSET = 1 << 0;  // Контакт 3 - сигнал RD0
  else            LATDCLR = 1 << 0;
  if (mask & 4)   LATFSET = 1 << 1;  // Контакт 4 - сигнал RF1
  else            LATFCLR = 1 << 1;
  if (mask & 8)   LATDSET = 1 << 1;  // Контакт 5 - сигнал RD1
  else            LATDCLR = 1 << 1;
  if (mask & 16)  LATDSET = 1 << 2;  // Контакт 6 - сигнал RD2
  else            LATDCLR = 1 << 2;
  if (mask & 32)  LATDSET = 1 << 9;  // Контакт 7 - сигнал RD9
  else            LATDCLR = 1 << 9;
  if (mask & 64)  LATDSET = 1 << 10; // Контакт 8 - сигнал RD10
  else            LATDCLR = 1 << 10;
  if (mask & 128) LATDSET = 1 << 3;  // Контакт 9 - сигнал RD3
  else            LATDCLR = 1 << 3;
}
 
void loop()
{
  // Опрашиваем кнопки.
  int button1_pressed = (digitalRead (button1) == LOW);
  int button2_pressed = (digitalRead (button2) == LOW);
 
  // Если кнопки были нажаты - запоминаем время.
  if (button1_pressed && ! button1_was_pressed)
    time1 = msec;
  if (button2_pressed && ! button2_was_pressed)
    time2 = msec;
  button1_was_pressed = button1_pressed;
  button2_was_pressed = button2_pressed;
 
  if (button1_pressed && button2_pressed) {
    // Обе кнопки нажаты: показываем разность.
    if (time1 > time2) {
      display (time1 - time2);
    } else {
      display (time2 - time1);
    }
  } else {
    // Отображаем текущее время, десятые доли секунды.
    display (msec / 100 % 10);
  }
  delay(1);
}
 
// Обработчик прерывания от таймера.
extern "C" {
  void __ISR (_TIMER_2_VECTOR, IPL3AUTO) timer2_handler(void)
  {
    // Сбрасываем флаг прерывания.
    mT2ClearIntFlag();
 
    // Наращиваем счётчик миллисекунд.
    msec++;
  }
}

5.2. Введение в ассемблер MIPS

Организация оперативной памяти, разбиение на слова. Понятие машинной инструкции и счётчика команд. Регистры общего назначения, номера и имена регистров. Передача параметров и возврат значения в регистрах. Регистр адреса возврата. Стек, регистр стека, место в стеке для каждой вызываемой функции (фрейм).

Основные инструкции системы команд MIPS. Разбиение по функциональным группам. Псевдоинструкции LI, LA.

Управление периферийными модулями: таймер, сигналы ввода-вывода. Концепция управляющих регистров, отображаемых на память.

5.3. Практическая работа на ассемблере

Среда разработки MPIDE. Использование JTAG-адаптера PICkit 3 для загрузки программ на плату.

Пример 1: (TODO) управление двумя светодиодами с помощью двух внешних кнопок. Тот же, что в разделе 5.1, но на ассемблере.

Задание для продвинутых студентов: переписать на ассемблере пример 2 из раздела 5.1.

Пример 5.3.1

Среда MPLABX. Управление двумя светодиодами с помощью двух внешних кнопок. Тот же, что в разделе 5.1, но на ассемблере.

TODO: нарисовать схему подключения кнопок. Требуются два резистора 10кОм для подтяжки к +3.3В.

// Пример для PIC32 на языке ассемблера.
// Файл имеет расширение .S - ассебмлер с препроцессором Си.
 
// Включаем набор стандартных определений для микроконтроллера PIC32.
#include <p32xxxx.h>
 
        .text                       // Начинаем секцию выполняемого кода.
        .set noreorder              // Отключаем переупорядочивание инструкций
 
//
// Функция ожидания, параметр в миллисекундах.
//
delay:  .globl  delay               // Помечаем метку delay как глобальную
 
        li      t0, 40000           // В зависимости от частоты процессора
        mul     t1, a0, t0          // Вычисляем нужное количество тактов
1:
        bne     t1, zero, 1b        // Крутим цикл ожидания
        addiu   t1, -1
 
        j       ra                  // Возврат
        nop
 
//
// Выполнение начинается с метки main.
//
main:   .global main                // Помечаем метку main как глобальную
 
        //
        // Включаем кэш, ускоряем обращение к памяти.
        //
        li      v0, 2               // Отключаем кэш, два такта ожидания
        la      t0, CHECON
        sw      v0, (t0)            // CHECON := 2
 
        li      v0, 0x40            // Ноль тактов ожидания для RAM
        la      t0, BMXCONCLR
        sw      v0, (t0)            // BMXCONCLR := 0x40
 
        li      v0, 0x30            // Включаем кэш
        la      t0, CHECONSET
        sw      v0, (t0)            // CHECONSET := 0x30
 
        mfc0    v0, _CP0_CONFIG
        ori     v0, 3
        mtc0    v0, _CP0_CONFIG     // Разрешаем кэширование сегмента kseg0
        ehb
 
        //
        // Сигналы от кнопок используем как входы.
        //
        li      v0, 1 << 8
        la      t0, TRISGSET
        sw      v0, (t0)            // Сигнал 11 (RG8) как вход от первой кнопки
 
        li      v0, 1 << 7
        la      t0, TRISGSET
        sw      v0, (t0)            // Сигнал 12 (RG7) как вход от второй кнопки
 
        //
        // Сигналы управления светодиодами - выходы.
        //
        li      v0, 1 << 6
        la      t0, TRISGCLR
        sw      v0, (t0)            // Сигнал 13 (RG6) - выход для светодиода 1
 
        li      v0, 1 << 0
        la      t0, TRISFCLR
        sw      v0, (t0)            // Сигнал RF0 - выход для светодиода 2
 
        la      s0, PORTG           // Адрес PORTG храним в регистре s0
        la      s1, LATGSET         // Адрес LATGSET храним в регистре s1
        la      s2, LATGCLR         // Адрес LATGCLR храним в регистре s2
        la      s3, LATFSET         // Адрес LATGSET храним в регистре s3
        la      s4, LATFCLR         // Адрес LATGCLR храним в регистре s4
 
loop:                               // Начало бесконечного цикла
        li      t0, 0               // Переменная need_wait в регистре s0
 
        //
        // Опрашиваем первую кнопку.
        //
        lw      v0, (s0)            // Опрашиваем порт G
        ext     v0, v0, 8, 1        // Сигнал RG8
        beq     v0, zero, 1f        // Переход, если кнопка нажата
        nop
 
        // Не нажата - гасим первый светодиод.
        li      v0, 1 << 6
        sw      v0, (s2)            // Запись в LATGCLR, сигнал RG6
        li      t0, 1               // Установка need_wait в 1
1:
        //
        // Опрашиваем вторую кнопку.
        //
        lw      v0, (s0)            // Опрашиваем порт G
        ext     v0, v0, 7, 1        // Сигнал RG7
        beq     v0, zero, 2f        // Переход, если кнопка нажата
        nop
 
        // Не нажата - гасим второй светодиод.
        li      v0, 1 << 0
        sw      v0, (s4)            // Запись в LATFCLR, сигнал RF0
        li      t0, 1               // Установка need_wait в 1
2:
        // Если надо, подождём.
        beq     t0, zero, 3f        // Переход, если need_wait равно 0
        nop
 
        jal     delay               // Вызов delay(150)
        li      a0, 150
3:
        //
        // Зажигаем оба светодиода.
        //
        li      v0, 1 << 6
        sw      v0, (s1)            // Запись в LATGSET, сигнал RG6
        li      v0, 1 << 0
        sw      v0, (s3)            // Запись в LATFSET, сигнал RF0
        jal     delay               // Вызов delay(50)
        li      a0, 50
 
        j       loop                // Бесконечный цикл
        nop
 
//
// Параметры конфигурации микроконтроллера PIC32
// для частоты 80MHz на плате Uno32.
//
        // Входной делитель PLL - 1:2
        // Умножитель PLL - 20x
        // Выходной делитель PLL - 1:1
        .section .config_BFC02FF4, code
        .word   0xfff8ffd9
 
        // Системная частота - первичный генератор с PLL
        // Первичный генератор - режим HS
        // Выход CLKO - включен
        // Частота периферии - 1:1
        .section .config_BFC02FF8, code
        .word   0xff60ce5b
 
        // Сигналы отладчика - PGC2/PGD2
        .section .config_BFC02FFC, code
        .word   0x7ffffffa

5.4. Концепция многозадачного выполнения

В основе многозадачности лежит стремление к простоте. В примере 5.1.1 нам приходилось обрабатывать всего два входных сигнала (кнопки). Тем не менее, основной цикл программы выглядит неочевидным. В реальных системах микроконтроллеру приходится обрабатывать десятки или даже сотни входных событий. Хороший способ справиться с этим - разбить программу на независимые части меньшего размера, выполняющиеся параллельно, не мешая друг другу. Такие части называют задачами (tasks). В больших операционных системах задачи, выполняющиеся в раздельных адресных пространствах, называются процессами.

Вот как могли бы выглядеть задачи в примере 5.1.1. Намного проще, не правда ли?

void task1()
{
    for (;;) {
        if (нажата первая кнопка) {
            гасим первый светодиод;
            delay (150);
        }
        зажигаем первый светодиод;
        delay (150);
    }
}

void task2()
{
    for (;;) {
        if (нажата вторая кнопка) {
            гасим второй светодиод;
            delay (150);
        }
        зажигаем второй светодиод;
        delay (150);
    }
}

Хотелось бы иметь возможность запускать на одном процессоре несколько задач, но чтобы они оставались независимыми и не мешали друг другу. Для этого нам понадобятся два новых понятия:

  1. Контекст выполнения
  2. Прерывания

Контекстом выполнения называется содержимое регистров процессора и стека выполняемой задачи. Ход программы полностью определяется контекстом выполнения. Если бы мы смогли в какой-то момент остановить процессор, запомнить куда-нибудь значения всех регистров (а указатель стека тоже находится в регистре), установить новые значения для регистров и пустить выполнение дальше, мы получили бы совсем другую задачу. Такое действие называется переключением контекста.

Если переключать контекст достаточно быстро, скажем 100 раз в секунду, будет создаваться впечатление параллельного и одновременного выполнения всех задач. Скажем, 10 миллисекунд работаем первая задача, потом 10 миллисекунд вторая, и дальше по кругу. Такой алгоритм распределения процессорного времени обычно называют циклическим (round robin). В больших операционных системах применяются и другие алгоритмы, более сложные.

Прерывания

(TODO)

5.5. Пример реализации многозадачности

(TODO)

MPIDE: пример с переключением двух задач по таймеру.

#include <plib.h>
 
// Кнопки на контактах 11 и 12.
const int button1 = 11;
const int button2 = 12;
 
// 7-сегментный индикатор на контактах 2-9.
const int segm_a = 2;
const int segm_b = 3;
const int segm_c = 4;
const int segm_d = 5;
const int segm_e = 6;
const int segm_f = 7;
const int segm_g = 8;
const int segm_h = 9;
 
// Размер стека для задач: пятьсот слов, или примерно два килобайта.
#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;
 
//
// Отображение одного сегмента на дисплее
//
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;
    }
}
 
//
// Функция ожидания, с остановом при нажатой кнопке.
//
void wait (int msec, int button)
{
    while (msec >= 5) {
        // Если нажата указанная кнопка - останавливаемся,
        // пока она не освободится.
        while (digitalRead (button) == LOW)
            ;
 
        delay (5);
        msec -= 5;
    }
}
 
//
// Первая задача: вращаем нижнее кольцо восьмёрки, сегменты D-E-G-C.
// Функция не должна возвращать управление.
//
void task1()
{
    for (;;) {
        display ('d', 1); wait (100, button1); display ('d', 0);
        display ('e', 1); wait (100, button1); display ('e', 0);
        display ('g', 1); wait (100, button1); display ('g', 0);
        display ('c', 1); wait (100, button1); display ('c', 0);
    }
}
 
//
// Вторая задача: вращаем верхнее кольцо восьмёрки, сегменты A-B-G-F.
// Функция не должна возвращать управление.
//
void task2()
{
    for (;;) {
        display ('a', 1); wait (150, button2); display ('a', 0);
        display ('b', 1); wait (150, button2); display ('b', 0);
        display ('g', 1); wait (150, button2); display ('g', 0);
        display ('f', 1); wait (150, button2); display ('f', 0);
    }
}
 
//
// Установка начального значения стека для запуска новой задачи.
//
int *create_task (int start, int *stack)
{
    stack += STACK_NWORDS - 36 - 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 [33] = 0x10000003;    // Status: CU0, EXL, IE
    stack [34] = 0;             // SRSCtl
    stack [35] = start;         // EPC: адрес начала
 
    return stack;
}
 
//
// Начальная инициализация программы.
//
void setup()
{
    // Сигналы от кнопок используем как входы.
    pinMode (button1, INPUT);
    pinMode (button2, INPUT);
 
    // Сигналы управления светодиодами - выходы.
    pinMode (segm_a, OUTPUT);
    pinMode (segm_b, OUTPUT);
    pinMode (segm_c, OUTPUT);
    pinMode (segm_d, OUTPUT);
    pinMode (segm_e, OUTPUT);
    pinMode (segm_f, OUTPUT);
    pinMode (segm_g, OUTPUT);
    pinMode (segm_h, OUTPUT);
 
    // Устанавливаем прерывание от таймера с частотой 100 Гц.
    OpenTimer2 (T2_ON | T2_PS_1_256, F_CPU / 256 / 100);
    ConfigIntTimer2 (T2_INT_ON | T2_INT_PRIOR_3);
 
    // Создаём две задачи.
    task1_stack_pointer = create_task ((int) task1, task1_stack);
    task2_stack_pointer = create_task ((int) task2, task2_stack);
}
 
//
// Основной цикл программы.
//
void loop()
{
    // Ничего не делаем, ждём прерывания от таймера.
    // После первого же прерывания начинают работать задачи.
}
 
//
// Обработчик прерывания от таймера.
//
extern "C" { __ISR (_TIMER_2_VECTOR, IPL3AUTO)
void timer2_handler()
{
    // Сбрасываем флаг прерывания.
    mT2ClearIntFlag();
 
    // Извлекаем значение указателя стека для текущей задачи.
    int *sp;
    asm volatile ("move %0, $sp" : "=r" (sp));
 
    // Переключаемся на другую задачу: меняем указатель стека.
    // Заметьте: в первый раз переменная current_task равна 0
    // и выполняется переключение со стека инициализации на
    // первую задачу.
    if (current_task == 1) {
        task1_stack_pointer = sp;
        sp = task2_stack_pointer;
        current_task = 2;
    } else {
        if (current_task == 2) 
            task2_stack_pointer = sp;
        sp = task1_stack_pointer;
        current_task = 1;
    }
 
    // Устанавливаем новое значение указателя стека. При выходе из 
    // функции из стека будут извлечены значения для остальных регистров.
    // Перечисляем здесь все регистры, которые необходимо сохранять и
    // и восстанавливать из стека при переключении контекста.
    // Компилятор сгенерирует нужные команды.
    asm volatile ("move $sp, %0" : : "r" (sp) :
                        "$1","$2","$3","$4","$5","$6","$7","$8","$9",
                        "$10","$11","$12","$13","$14","$15","$16","$17",
                        "$18","$19","$20","$21","$22","$23","$24","$25",
                        "$30","$31","hi","lo","sp");
}
}

MPLABX: пример с прерыванием от таймера

(TODO: адаптировать предыдущий пример для MPLABX)

#include <plib.h>       // Include to use PIC32 peripheral libraries
 
// Define system operating frequency
#define F_CPU           80000000
 
void delay (int msec)
{
    int count = F_CPU/2000 * msec;
 
    while (count > 0)
        count--;
}
 
int main()
{
    SYSTEMConfig (F_CPU, SYS_CFG_ALL);
    INTConfigureSystem (INT_SYSTEM_CONFIG_MULT_VECTOR);
 
    // Enable timer interrupt at 10 Hz.
    OpenTimer2 (T2_ON | T2_PS_1_256, F_CPU / 256 / 5);
    ConfigIntTimer2 (T2_INT_ON | T2_INT_PRIOR_3);
 
    // Initialize the digital pins as an output.
    TRISGCLR = 1 << 6;                  // RG6 - upper LED
    TRISFCLR = 1 << 0;                  // RF0 - lower LED
 
    for (;;) {
        LATGCLR = 1 << 6;               // Set the upper LED off
        delay (200);                    // Wait
        LATGSET = 1 << 6;               // Set the upper LED on
        delay (200);                    // Wait
    }
}
 
void __ISR (_TIMER_2_VECTOR, IPL3AUTO) timer2_handler(void)
{
    //mT2ClearIntFlag();                // Clear interrupt flag
    IFS0CLR = 1 << _TIMER_2_IRQ;
 
    LATFINV = 1 << 0;                   // Invert RF0 - lower LED
}
 
/*
 * Device configuration bits.
 */
#pragma config FNOSC    = PRIPLL        // Primary oscillator with PLL
#pragma config POSCMOD  = XT            // XT oscillator
#pragma config FPLLMUL  = MUL_20        // PLL multiplier = 20x
#pragma config FPLLIDIV = DIV_2         // PLL divider = 1/2
#pragma config FPLLODIV = DIV_1         // PLL postscaler = 1/1
#pragma config FPBDIV   = DIV_8         // Peripheral bus clock = SYSCLK/8
 
proj/mips/lab5-mplab.txt · Последние изменения: 2012/05/08 18:18
 
Copyright (C) 1996-2013 Serge Vakulenko
serge@vak.ru