Миро Самек. Построение простых систем на ARM-контроллерах с использованием инструментов GNU (перевод)


Часть 10 - Стратегии отладки

В финальной части рассматривается проект "Blinky", сопровождающий данную публикацию. Кроме того, даётся несколько советов по проверке различных dариантов перехвата управления при прерываниях в ручном режиме.


10.1 Учебный проект "Blinky"

Учебный проект называется "Blinky" потому, что он мигает (blink) четырьмя светодиодами отладочной платы AT91SAM7S-EK. Проект представляет собой простое приложение, содержащее приоритетную и фоновую задачи (обработчики прерываний и основной программный цикл) и спроектированное для демонстрации всех описанных в данной статье подходов и возможностей.

 
Рисунок 10.1 Отладочная плата AT91SAM7S-EK фирмы Atmel
Samek_bare_metal_Part10Fig1.jpg

А именно, "Blinky" основан на правильной инициализации сегментов ".data", ".bss", ".rodata", ".text" и ".stack". Вся низкоуровневая инициализация по настройке PLL контроллера AT91SAM для генерации тактовой частоты 48MHz и изменению карты памяти написана на языке "Си" (см. вторую часть статьи).

Для увеличения быстродействия часто используемые функции расположены в оперативной памяти (см. пятую часть статьи), причём, использованы оба варианта размещения: прямым заданием атрибута размещения "__attribute__ ((section (".text.fastcode")))" и указанием места расположения функции в управляющем файле компоновщика "blinky.ld". Кроме того, некоторые файлы проекта скомпилированы под набор инструкций ARM, а оставшиеся - под набор инструкций THUMB, для демонстрации взаимодействия обоих наборов (см. раздел, посвящённый "Makefile").

C++ версия проекта (расположенная в каталоге "cpp_blinky") основана на вызовах статического конструктора и использует для демонстрации виртуальные функции с поздним и ранним связыванием. Сравнив "*.map" файлы, можно заметить, что версия для C++ больше, чем версия для "Си", всего на 500 байт, но, при этом, за счёт поддержки полиморфизма может гораздо больше, чем вариант для "Си".

"Blinky" активно использует прерывания, возникающие с очень высокой частотой (в среднем около 47kHz или каждые 1000 тактов процессорного времени). Уровень прерываний - уровень приоритетной задачи - состоят из прерываний от трёх источников, как-то:

1. прерывание от программируемого интервального таймера контроллера AT91SAM7 (Programmable Interval Timer - PIT) возникает 100 раз в секунду и настроено, как низкоприоритетное IRQ прерывание;

2. прерывание от блока сравнения таймера 0 (Timer0 RC-compare) возникает раз в 1000 тактов частоты MCK/2 (около 24kHz) и настроено как высокоприоритетное IRQ прерывание, и

3. прерывание от блока сравнения таймера 1 (Timer1 RC-compare) возникает раз в 999 тактов частоты MCK/2 (около 24kHz) и настроено как FIQ прерывание посредством возможности "быстрой активации" ("fast forcing") контроллера AT91SAM7S [15].

Для тактирования таймеров Timer0 и Timer1 специально выбран источник максимально возможной частоты - MCK/2, а близкие частоты работы вызывают медленный сдвиг разницы фаз обоих прерываний на два такта частоты процессора за один цикл прерывания.

Это вызывает перекрытие прерываний и в долговременной перспективе приводит к попытке прервать каждую машинную инструкцию, включая инструкции обработчиков IRQ и FIQ прерываний. Перекрытие прерываний от Timer0 IRQ и Timer1 FIQ происходит с частотой биения (MCK/2)/(1000*999), то есть приблизительно 27 раз в секунду.

Обработчики прерываний (ISR) взаимодействуют с фоновым циклом (и, потенциально, друг с другом) через флаги, собранные в битовом массиве. Все ISR напоминают фоновому циклу о себе через функцию "eventFlagSet()", использующую для защиты битового массива критическую секцию (см. седьмую часть статьи).

Листинг 10.1 показывает структуру фонового цикла. Цикл проверяет наличие новых событий вызовом функции "eventFlagCheck()".

Данная функция проверяет нужный флаг и, если он установлен, сбрасывает его внутри критической секции. Для каждого установленного флага события фоновый цикл вызывает метод "dispatch()", принадлежащий соответствующему объекту класса "Blinky", который скрывает один светодиод на отладочной плате AT91SAM7S-EK.

 
Листинг 10.1 Фоновый цикл проекта "Blinky" (версия для языка C++)

      static Blinky blinky_pit;     /* instance of "class" Blinky (.bss section) */
      static Blinky blinky_timer0;  /* instance of "class" Blinky (.bss section) */
      static Blinky blinky_timer1;  /* instance of "class" Blinky (.bss section) */
      static Blinky blinky_idle;    /* instance of "class" Blinky (.bss section) */

      static Blinky *pBlinky[] = {  /* pointers to Blinky (.data section) */
            &blinky_pit,
            &blinky_timer0,
            &blinky_timer1
      };

      int main (void) {             /* explicit "constructor" calls */
            Blinky_ctor(&blinky_pit,    1,     9,     1);
            Blinky_ctor(&blinky_timer0, 2,  9000,  1000);
            Blinky_ctor(&blinky_timer1, 3,  9000,  1000);
            Blinky_ctor(&blinky_idle,   0, 18000,  2000);

            BSP_init();             /* initialize the board support package */

            for (;;) {              /* for-ever */
                  if (eventFlagCheck(PIT_FLAG)) {
                        Blinky_dispatch(pBlinky[PIT_FLAG]);
                  }

                  if (eventFlagCheck(TIMER0_FLAG)) {
                        Blinky_dispatch(pBlinky[TIMER0_FLAG]);
                  }

                  if (eventFlagCheck(TIMER1_FLAG)) {
                        Blinky_dispatch(pBlinky[TIMER1_FLAG]);
                  }

                  Blinky_dispatch(&blinky_idle);
            }

            /* unreachable; this return is only to avoid compiler warning */
            return 0;
      }
										

С точки зрения пользователя, основная задача объекта "Blinky" - деление частоты появления обрабатываемых событий до уровня, при котором мигание светодиода воспринимается человеческим глазом.

Рисунок 10.2 показывает жизненны цикл класса "Blinky", который является конечным автоматом с двумя состояниями "ON" и "OFF". В обоих состояниях при появлении события (вызове метода "Blinky::dispatch()") автомат уменьшает счётчик задержки и, когда счётчик становится равным нулю, меняет состояние на противоположное.

Например, на рисунке 10.2 задержка составляет три такта в состоянии "OFF" и два такта в состоянии "ON". В результате светодиод мигает с частотой в пять раз меньше частоты появления событий со скважностью 2/3.

(Примечание. Версия приложения "Blinky" на языке "Си" эмулирует отсутствующую в языке концепцию классов. Эта простая техника может быть интересна сама по себе. Кроме того, достаточно несложна реализация на языке "Си" наследования и полиморфизма [17]).

 

      #ifndef MANUAL_TEST
            AT91C_BASE_TC0->TC_RC = 1000;       /* Timer0 reset compare C */
            AT91C_BASE_TC1->TC_RC = 1000 - 1;   /* Timer1 reset compare C */

            AT91C_BASE_TC0->TC_CCR = AT91C_TC_SWTRG;  /* start Timer0 */
            AT91C_BASE_TC1->TC_CCR = AT91C_TC_SWTRG;  /* start Timer1 */
      #else
            AT91C_BASE_TC0->TC_RC = 1;          /* Timer0 reset compare C (just one tick) */
            AT91C_BASE_TC1->TC_RC = 1;          /* Timer1 reset compare C (just one tick) */
      #endif

            ARM_INT_UNLOCK(0x1F);               /* unlock IRQ/FIQ at the ARM core level */
      }
										
 
Рисунок 10.2 Жизненный цикл объекта "Blinky": конечный автомат (a) и диаграмма состояний (b)
Samek_bare_metal_Part10Fig2.jpg


10.2 Проверка вариантов перехвата управления при прерываниях в ручном режиме

Приложение "Blinky" активно тестирует принципы реализации обработчиков прерываний, обсуждавшийся в 6, 7, 8 и 9 частях статьи.

Постоянное изменение относительной фазы и высокая частота появления прерываний обеспечивают широкие возможности для передачи управления между IRQ и FIQ прерываниями и фоновым циклом.

Здесь будет описана техника ручного тестирования различных вариантов возникновения прерываний, позволяющая активировать прерывание на любой машинной инструкции и наблюдать вызываемую им передачу управления.

Приложение "Blinky" содержит специальные возможности для тестирования прерываний в ручном режиме. Чтобы сконфигурировать прерывания Timer0 и Timer1 для проверки, надо раскомментировать определение макроса "MANUAL_TEST" в начале файла "bsp.c". Как можно увидеть в приведённом ниже коде, оба таймера настроены под один такт счёта в регистре сравнения (RC-compare) и запрещённым программным вызовом прерываний.

 
Листинг 10.2 Настройка проекта "Blinky" для проверки в ручном режиме

      void BSP_init(void) {
            ...

      #ifndef MANUAL_TEST
            AT91C_BASE_TC0->TC_RC = 1000;       /* Timer0 reset compare C */
            AT91C_BASE_TC1->TC_RC = 1000 - 1;   /* Timer1 reset compare C */

            AT91C_BASE_TC0->TC_CCR = AT91C_TC_SWTRG;  /* start Timer0 */
            AT91C_BASE_TC1->TC_CCR = AT91C_TC_SWTRG;  /* start Timer1 */
      #else
            AT91C_BASE_TC0->TC_RC = 1;          /* Timer0 reset compare C (just one tick) */
            AT91C_BASE_TC1->TC_RC = 1;          /* Timer1 reset compare C (just one tick) */
      #endif

            ARM_INT_UNLOCK(0x1F);               /* unlock IRQ/FIQ at the ARM core level */
      }
										

После разрешения ручного режима становится возможно вызывать прерывания из отладчика, записывая заначения 0x05 (SWTRG + CLKEN) в регистр управления таймера (Control Register - TCx_CR). Регистр управления для Timer0 (TC0_CR) расположен по адресу 0xFFFA0000, а для Timer1 (TC1_CR) - по адресу 0xFFFA0040 [15]. Единственный такт счёта в регистре сравнения (RC-comapre) вызывает немедленное окончание счёта и срабатывание прерывания таймера до начала следующей машинной инструкции.

Основной метод отладки заключается в прерывании работы программы в интересной с точки зрения перехвата управления точке, расстановке контрольных точек для проверки пути передачи управления и вызове интересующего прерывания.

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

Первым интересным тестом является проверка приоритизации IRQ прерываний контроллером AIC. Для проверки следует установить контрольную точку внутри "ISR_pit()" (настроенного как низкоуровневое IRQ прерывание).

После срабатывания контрольной точки надо поставить новую точку на следующей машинной инструкции, вторую точку - на первой инструкции обработчика прерываний "ARM_irq()", вызвать прерывание Timer0, записав значения 0x05 по адресу 0xFFFA0000, после чего запустить свободный прогон программы, нажав кнопку "Run".

Правильная последовательность появления событий выглядит следующим образом:

1. Сначала срабатывает точка останова внутри "ARM_irq()". Это означает, что IRQ прерывание от Timer0 перехватывает управление у IRQ прерывания PIT.
Примечание: после срабатывания этой контрольной точки надо запретить повторное окончание счёта Timer0, записав значение 0x02 (CLKDIS) по адресу 0xFFFA0000 (TC0_CR).

2. Второй срабатывает точка останова внутри "ISR_pit()". Это означает, что низкоприоритетрое прерывание IRQ продолжило работу после завершения IRQ прерывания от Timer0. Обе точки останова следует убрать перед проведением следующего теста.

Далее интересно убедиться, что прерывания FIQ перехватывают управление у IRQ, но только вне критических секций. Начинать надо с постановки контрольной точки внутри какой-либо критической секции, скажем, внутри функции "eventCheckFlag()", после чего запустить приложение.

Дождавшись срабатывания точки останова, надо активировать прерывания обоих таймеров Timer0 и Timer1, записав значения 0x05 по адресам 0xFFFA0000 (TC0_CR) и 0xFFFA0040 (TC1_CR). Перед запуском программы на свободный прогон следует поставить контрольные точки на следующую машинную инструкцию, на вторую инструкцию после выхода из критической секции (инструкция "BX r14"), на первой инструкции "ARM_irq()" и на первой инструкции "ARM_fiq()".

Правильная последовательность появления событий выглядит следующим образом:

1. Первой срабатывает контрольная точка внутри "eventCheckFlag()". Это означает, что прерывания не могут перехватить управление внутри критической секции.

2. Второй срабатывает точка внутри обработчика "ARM_fiq()". Это показывает, что прерывания FIQ выигрывает соревнование с IRQ.
Примечание: в этой точке необходимо запретить повторное окончание счёта Timer1 записью значения 0x02 (CLKDIS) по адресу 0xFFFA0040 (TC1_CR).

3. Третьей срабатывает точка останова в обработчике "ARM_irq()": прерывание IRQ запускается сразу после завершения работы обработчика FIQ до возврата в код приложения.
Примечание: в этой точке необходимо запретить повторное окончание счёта Timer0 записью значения 0x02 (CLKDIS) по адресу 0xFFFA0000 (TC0_CR).

4. Последней получает управление контрольная точка в конце функции "eventCheckFlag()", после того как отработает код всех обработчиков прерываний.

Данная техника может успешно использоваться и при проведении других тестов, прояснющих поведение системы при различных вариантах передачи управления.

ПредпросмотрAttachmentSize
bare_metal_arm_systems_html.zip327.73 КБ
blinky_files.zip173.94 КБ