Здесь приведено детальное описание, как начать программировать микроконтроллеры ARM. Это минималистичное, простое руководство, позволяющее понять принцип написания и запуска программ на платформе AT91SAM7S (перевод руководства [1]).
Микроконтроллеры Atmel серии AT91SAM7S64 очень хорошо подходят для быстрого старта разработки на ARM: они относительно простые и дешевые (AT91SAM7S64 в корпусе TQFP64 стоит на AliExpress около 113 рублей, или $2), в них есть встроенное ОЗУ (RAM) и память программ (FLASH) порт JTAG, предназначенный для записи памяти и отладки, и множество аппаратных устройств для ввода/вывода. Кроме того, все программное обеспечение, которое нужно для разработки, доступно бесплатно.
[Требуемое ПО]
Сначала Вам нужно получить все необходимое программное обеспечение. Если Вы пользователь Windows, и спокойно относитесь к нарушению лицензионных соглашений, то самый лучший выбор для старта - Windows, пакет разработки IAR и один из клонов JTAG J-Link, которые можно недорого купить на AliExpress (цена не более 1200 рублей, т. е. около $21). Но эта статья не об этом, здесь будем использовать только свободное ПО.
Конечно, Вы можете использовать также интегрированные среды разработки (IDE) наподобие Eclipse, однако описание их настройки в этой статье не рассматривается. Предположительно у Вас есть текстовый редактор и базовый опыт, как писать программы с помощью инструментов GNU (make, gcc). Кроме того, эта статья демонстрирует, как выполнять разработку по Linux, хотя мало отличий того же самого процесса в среде Windows и других операционных систем.
Итак, сначала Вам понадобится как минимум следующее:
• ARM-GCC: это набор утилит "GNU toolchain", содержащий компилятор, ассемблер и линкер для кросс-платформенной разработки программ ARM (словечко "кросс" означает, что разработка для ARM ведется в среде другой среды выполения, т. е. Linux или Windows). После установки программ Вы получите утилиты arm-elf-gcc, arm-elf-as и arm-elf-ld. Пользователи Linux и Mac OS X могут найти инструкции по инсталляции и готовые бинарники в статье [2]. Пользователи Windows найдут отличный пакет всего необходимого в YAGARTO [3].
• OpenOCD: open on-chip debugger, открытый и бесплатный отладчик [4]. Если Вы пользователь Linux, то рекомендуется сделать выборку исходного кода из репозитория SVN и компиляция этого кода для получения самых свежих бинарников OpenOCD. Как это делается, см. описание на официальном сайте OpenOCD. Пользователи Windows могут использовать OpenOCD из состава YAGARTO [3].
• Обычные утилиты наподобие GNU make, должна быть среда для исполнения команд (shell), текстовый редактор и т. д. Пользователи Linux и Mac OS X могут использовать свои штатные утилиты. Пользователи Windows получат все необходимое вместе с YAGARTO [3].
[Требуемая аппаратура]
После установки ПО понадобится найти следующее железо:
• Кабель JTAG: он подключается с одной стороны к программируемому микроконтроллеру ARM, и с другой стороны к PC. Есть множество кабелей JTAG, но следует выбрать тот, который поддерживается OpenOCD. Обычно это может быть кабель на основе чипа FT2232H [5]. Автор использовал самодельный кабель USB-AtmelPrg [6].
Самый дешевый кабель для JTAG-интерфейса обычно называют "wiggler", он подключается к параллельному порту принтера PC (порт LPT), и это также поддерживается OpenOCD. Вы можете легко сделать wiggler самостоятельно, его схема доступна в Интернет, однако имейте в виду, что многие жалуются на ошибки и нестабильную работу этого устройства. Некоторые пользователи используют кабель Amontec JTAGKey-Tiny, он довольно недорогой (около $40), с интерфейсом USB и поддержкой OpenOCD. Также хороший вариант - кабель Turtelizer2 [7].
• Плата с микроконтроллером AT91SAM7S64: на ней мы будем запускать простейшую программу "hello-world". На плате может быть микроконтроллер AT91SAM7S64 или AT91SAM7S256. Вы можете купить плату разработчика AT91SAM7S-EK от Atmel на Digikey примерно за $150 или собрать самостоятельно [8], много подобных плат есть на AliExpress. Автор использовал простейшую плату с минимальным набором компонентов, которые требуются для запуска микроконтроллера. Должен быть кварц на частоту 18.432 МГц, несколько блокирующих конденсаторов и верхние подтягивающие резисторы и стабилизатор напряжения питания 3.3V.
На фотографии ниже показана минималистическая конфигурация системы: плата AT91SAM7S справа внизу (её схема показана во врезке выше). На фотографии не видны кварц и блокирующие питание конденсаторы, потому что они находятся на нижней стороне печатной платы.
[Простейшая программа Hello-World]
Здесь приведена простая демонстрационная программа примера для микроконтроллеров AT91SAM7S.
Исходный код: minimalistic-hello-world-1.0.tar.gz [9] (61 килобайт source tarball, сжатый архиватором gzip). Версия 1.0 (2007-07-19), автор Wolfgang Wieser, опубликован по лицензии GNU GPL (Version 2), требует компилятора ARM-GCC.
В архиве minimalistic-hello-world-1.0.tar.gz находятся следующие файлы:
Файл
Назначение
AT91SAM7S64.h
Файл из библиотек Atmel, он содержит определения аппаратуры, которая находится внутри микроконтроллера AT91SAM7S64.
AT91SAM7S256.h
То же самое для микроконтроллера AT91SAM7S256.
AT91SAM7.h
Символьная ссылка на один из вышеуказанных файлов. Пользователи Windows могут просто сделать копию файла AT91SAM7S64.h или AT91SAM7S256.h в рабочем каталоге проекта под именем AT91SAM7.h.
main.c
Основной код программы примера "hello-world", написанный на языке C.
crt.s
Низкоуровневый код запуска микроконтроллера (startup code), написанный на ассемблере.
Makefile
Обычный командный файл для утилиты GNU make.
ld_flash.cmd
Скрипт команд линкера (см. описание ниже).
openocd-flash.cfg
Файл конфигурации OpenOCD, предназначенный для загрузки двоичного кода программы в память микроконтроллера AT91SAM7S.
openocd.cfg
Такой же файл конфигурации, но без инструкций для прошивки памяти.
openocd_doflash
Целевой скрипт OpenOCD, который содержит реальные команды прошивки памяти.
В действительности программа микроконтроллера содержится только в двух модулях, это файлы main.c и crt.s.
Файл crt.s это низкоуровневый код ассемблера, предназначенный для запуска ядра микроконтроллера. Он инициализирует статические переменные, настраивает стеки и обработчики прерываний (IRQ handlers), и в завершении всех своих действий передает управление в функцию main(), написанную на языке C. Код ассемблера в этой статье не обсуждается, читайте комментарии в архиве [9].
Ниже показан исходный код модуля main.c. Многие для упрощения подробные комментарии здесь удалены, подробности см. в [9]. Функция Initialize() выполняет инициализацию используемой аппаратуры, включая подключение кварцевого генератора. Функция PanicBlinker() никогда не делает возврат, она используется для диагностики ошибок наподобие недопустимого доступа к памяти. Она вызывается из crt.s.
/*
* main.c - простейшая программа AT91SAM7 (hello world).
*
* Copyright (c) 2007 by Wolfgang Wieser.
*/
#include "AT91SAM7.h"
typedefsignedchar int8;
typedefunsignedchar uint8;
#define nop() __asm__ __volatile__("nop")
//Ножки для управления светодиодами
#define LED_A (1U << 0) /* PA0, pin 48 */
#define LED_B (1U << 1) /* PA1, pin 47 */
//Обработчики прерываний:
staticvoidDefaultInterruptHandler(void)
{ /* Тут нет никаких действий. */ }
staticvoidSpuriousInterruptHandler(void)
{ /* Здесь тоже никаких действий, просто быстрый возврат. */ }
// Бесконечный цикл, в котором мигает светодиод LED_A (PA0).
// Это используется для диагностики ошибок.
externvoidPanicBlinker(uint8 code);
voidPanicBlinker(uint8 code)
{
volatile AT91PS_PIO pPIO = AT91C_BASE_PIOA;
pPIO->PIO_PER |= LED_A; // Разрешает PIO управлять светодиодом.
pPIO->PIO_OER |= LED_A; // Разрешение выхода.
pPIO->PIO_SODR = LED_A; // Начало с выключенным светодиодом.for(;;)
{
uint8 i;
unsignedint j;
for(i=0; i < code; i++)
{
pPIO->PIO_CODR = LED_A; // лог. 0, светодиод включается.for(j=300000; j; j--) nop();
pPIO->PIO_SODR = LED_A; // лог. 1, светодиод выключается.for(j=300000; j; j--) nop();
}
// Цикл для некоторой задержки:for(j=300000*3; j; j--) nop();
}
}
// Функция для инициализации аппаратуры.
staticvoidInitialize(void)
{
// Установка количества циклов ожидания для доступа к FLASH (Flash Wait sate).// Здесь устанавливается нулевое количество циклов ожидания (0 wait states).
AT91C_BASE_MC->MC_FMR = ((AT91C_MC_FMCN)&(22<<16)) | AT91C_MC_FWS_0FWS;
// Запрет сторожевого таймера (watchdog).
AT91C_BASE_WDTC->WDTC_WDMR= AT91C_WDTC_WDDIS;
// Запуск основного генератора.
AT91PS_PMC pPMC = AT91C_BASE_PMC;
pPMC->PMC_MOR = (( AT91C_CKGR_OSCOUNT & (6U<<8)) | AT91C_CKGR_MOSCEN );
while(!(pPMC->PMC_SR & AT91C_PMC_MOSCS));
// Выбор в качестве тактовой частоты master clock (MCK).// Это основной генератор, стабилизированный кварцем.
pPMC->PMC_MCKR = AT91C_PMC_CSS_MAIN_CLK;
while(!(pPMC->PMC_SR & AT91C_PMC_MCKRDY));
pPMC->PMC_MCKR = AT91C_PMC_CSS_MAIN_CLK | AT91C_PMC_PRES_CLK;
while(!(pPMC->PMC_SR & AT91C_PMC_MCKRDY));
// Разрешение сброса пользователя. Это помогает в отладке.
AT91C_BASE_RSTC->RSTC_RMR =0xa5000400U| AT91C_RSTC_URSTEN;
// Настройка обработчиков прерывания по умолчанию.// 0 это FIQ, 1 это SYS.int i;
for(i=0; i <31; i++)
{ AT91C_BASE_AIC->AIC_SVR[i] = (unsigned)&DefaultInterruptHandler; }
AT91C_BASE_AIC->AIC_SPU = (unsigned)&SpuriousInterruptHandler;
// Настройка ножек ввода/вывода для управления светодиодами.volatile AT91PS_PIO pPIO = AT91C_BASE_PIOA;
// Разрешает PIO для управления светодиодами:
pPIO->PIO_PER = LED_A | LED_B;
// Разрешает выходы на ножках, куда подключены светодиоды:
pPIO->PIO_OER = LED_A | LED_B;
// Устанавливает начальное состоние выходов в лог. 1, при этом// светодиоды выключены:
pPIO->PIO_SODR = LED_A | LED_B;
}
intmain(void)
{
Initialize();
// *(int*)0x800000=177; // < -- Приведет к ошибке data abort.// (*((void(*)(void))0x800000))(); // < -- Приведет к ошибке prefetch abort.// Переключает светодиоды, на очень высокой скорости (скорость переключения// зависит от тактовой частоты процессора). Переключение настолько быстрое,// что его можно увидеть только с помощью осциллографа.volatile AT91PS_PIO pPIO = AT91C_BASE_PIOA;
for(;;)
{
// Первое переключение:
pPIO->PIO_CODR = LED_A;
pPIO->PIO_SODR = LED_A;
// Второе переключение:
pPIO->PIO_CODR = LED_A;
pPIO->PIO_SODR = LED_A;
// Небольшая задержка:int i;
for(i=0; i <10; i++) nop();
}
return(0);
}
Из этого кода можно увидеть, что функция main() сначала инициализирует аппаратуру, и затем входит в бесконечный цикл, который дергает ножкой LED_A (PA0, pin 48). Поскольку программа активировала основной генератор, то подразумевается, что к микроконтроллеру подключен кварцевый резонатор. Можно использовать любой кварцевый резонатор, но чаще всего используют кварц на частоту 18.432 МГц, потому что он хорошо подходит для большинства приложений, включая реализацию интерфейса USB на микроконтроллере AT91SAM7S. В этом примере блок PLL не используется, поэтому не требуется применение фильтра второго порядка, подключенного к ножке PLLRC.
Имейте в виду, что мигание настолько быстрое, что без осциллографа Вы его не заметите. Чтобы мигание стало заметным без осциллографа, необходимо между переключениями ножки порта вставить циклы задержки, как это сделано в подпрограмме PanicBlinker().
[Компиляция программы]
Конечно, Вы можете скомпилировать эту программу в двоичный файл простым вызовом make из корневой папки проекта, однако следует знать о некоторых требуемых шагах:
1. Сначала crt.s ассемблируется в объектный файл crt.o, и main.c компилируется в объектный файл main.o:
Опция -fno-common скорее всего не нужна, но она защитит Вас от некоторых возможных ошибок. Подробнее см. документацию по компилятору GCC.
2. Полученные объектные файлы должны быть линкованы для получения двоичного исполняемого кода. Обратите внимание, что линкеру передается опция -nostartfiles поскольку у нас уже есть собственный код запуска в crt.o. При компиляции любых проектов убедитесь, что указали линковку кода запуска вместе со всеми другими необходимыми библиотеками. Области памяти и их размеры для стека (stack) и кучи (heap) определены в скрипте линкера ld_flash.cmd:
После этого шага получится двоичный файл main.elf в формате ELF. В нем содержится также отладочная информация, потому что программа была скомпилирована с опцией -g.
3. Теперь нужно получить двоичные данные программы, чтобы их можно было прошить в память программ (FLASH) микроконтроллера. Двоичный исполняемый код для прошивки извлекается из файла ELF:
Файл main.bin может быть теперь записан в энергонезависимую память FLASH микроконтроллера AT91SAM7S. После этого сброс или выключение/включение питания приведет к запуску нашей программы, и можно будет наблюдать (осциллографом) мерцание светодиода LED_A.
Скрипт линкера ld_flash.cmd очень важная часть всего процесса, в нем содержится следующее:
/* Здесь указывается точка входа в программу, entry point
(адрес _vec_reset определен в файле crt.s). */
ENTRY(_vec_reset)
/* Определение глобального символа _stack_end (обозначение конца стека): */
_stack_end =0x203FFC; /* AT91SAM7S64 */
/* Определяются выходные секции: */
SECTIONS
{
. =0; /* установка в 0 текущего счетчика адреса */
.text :/* коллекция всех секций, которые должны быть в памяти
FLASH после холодного старта */
{
*(.text) /* все секции .text (код программы, code) */*(.rodata) /* все секции .rodata (константы, строки и т. п.) */*(.rodata*) /* все секции .rodata* (константы, строки и т. п.) */*(.glue_7) /* все секции .glue_7 (непонятно, для чего это) */*(.glue_7t) /* все секции .glue_7t (непонятно, для чего это) */
_etext = .; /* определение глобального символа _etext сразу после
последнего байта кода */
} >flash /* команда разместить все вышеперечисленное в память
FLASH */
.data :/* коллекция всех инициализированных секций .data,
которые попадают в RAM */
{
_data = .; /* создание глобального символа, помечающего
начало секции .data */*(.data) /* все секции .data */
_edata = .; /* определение глобального символа, помечающего
конец секции .data */
} >ram AT >flash /* команда поместить все вышеперечисленное в RAM
(но загрузить копию инициализатора LMA во FLASH) */
.bss :/* коллекция всех не инициализированных секций .bss,
которые попадают в RAM */
{
_bss_start = .; /* определение глобального символа, помечающего
начало секции .bss */*(.bss) /* все секции .bss */
} >ram /* команда поместить все вышеперечисленное в RAM;
все эти данные будут очищены кодом запуска startup */
. = ALIGN(4); /* увеличение текущего счетчика адреса на границу,
выровненную на 32-битный байтовый адрес */
_bss_end = . ; /* определение глобального символа, помечающего
конец секции .bss */
}
_end = .; /* определение глобального символа, помечающего
конец RAM приложения */
Важное замечание: представленная здесь простейшая программа hello-world предназначена для запуска на AT91SAM7S64. В случае использования AT91SAM7S256 нужно сделать следующее:
• Пользователи Linux: сделайте символьную ссылку AT91SAM7.h, чтобы она указывала на файл AT91SAM7S256.h вместо файла AT91SAM7S64.h. Пользователи Windows: выполните копию файла AT91SAM7S256.h (или переименуйте его) в файл AT91SAM7.h.
• Поменяйте определения памяти и стека в скрипте линкера ld_flash.cmd, поскольку память AT91SAM7S256 в четыре раза больше (это относится и к FLASH, и к RAM):
Теперь все готово запустить нашу программу на целевом железе. Чтобы поместить двоичный код программы в содержимое памяти FLASH микроконтроллера, можно просто выполнить команду make flash. Это даст сигнал для OpenOCD загрузить main.bin. Но перед тем, как это можно будет сделать, убедитесь, что OpenOCD корректно сконфигурирована. Для этого нужно посмотреть на содержимое openocd.cfg:
telnet_port 4444
gdb_port 3333
# Команды, специально относящиеся к USB-AtmelPrg.
# Их нужно изменить, если используете другой адаптер JTAG.
interface usbatmelprg
jtag_speed 0
jtag_nsrst_delay 200
jtag_ntrst_delay 200
reset_config srst_only srst_pulls_trst
# Целевой процессор из серии AT91SAM7:
jtag_device 4 0x1 0xf 0xe
target arm7tdmi little run_and_halt 0 arm7tdmi
run_and_halt_time 0 30
flash bank at91sam7 0 0 0 0 0
daemon_startup reset
Обычно нужно только поменять определение адаптера JTAG. Предыдущая конфигурация предназначена для самодельного кабеля USB-AtmelPrg [6], но если Вы используете Amontec JTAGKey, то можете заменить секцию JTAG следующим содержимым:
Затем хорошей идеей будет быстро протестировать соединение с JTAG путем запуска OpenOCD, для этого откройте два терминала. В первом выполните команду openocd -f openocd.cfg, см. сессию терминала ниже.
bash# openocd -f openocd.cfg
Info: ../../trunk/src/openocd.c:86 main(): Open
On-Chip Debugger (2007-06-28 12:30 CEST)
Opened USB connection to 0403:6003:
Hardware: USB-AtmelPrg, Rev. 2/2
Info: ../../../trunk/src/jtag/usbatmelprg.cc:21
4 usbatmelprg_init():
Connected to 0403:6003 (USB-AtmelPrg, Rev. 2/2):
Device: USB AtmelPrg Rev 2.3
Vendor: Wolfgang WIESER, 07/2007
Firmware version: 0x203
Protocol version: 5 Warning: ../../../trunk/src/target/arm7_9_common.c
:685 arm7_9_assert_reset(): srst resets test logic
, too
Info: ../../../trunk/src/server/server.c:67 add
_connection(): accepted 'telnet' connection from 0
Info: ../../../trunk/src/server/server.c:367 se
rver_loop(): dropped 'telnet' connection
Во второй сессии терминала откройте подключение telnet командой telnet localhost 4444, см. сессию ниже.
bash# telnet localhost 4444
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
Open On-Chip Debugger
> poll
target state: halted
target halted in ARM state due to debug
request, current mode: System
cpsr: 0x800000df pc: 0x000005e4
> resume
Target 0 resumed
> halt
requesting target halt...
> Target 0 halted
target halted in ARM state due to debug
request, current mode: System
cpsr: 0x800000df pc: 0x000005e0
> shutdown
Connection closed by foreign host.
Пользователи Windows могут запустить сессию telnet с помощью запуска cmd.exe из меню Пуск, и затем подключиться к порту 4444 по имени localhost, которое соответствует адресу 127.0.0.1.
Как Вы можете видеть, сессия telnet позволяет выполнять команды OpenOCD. Для получения полного списка команд используйте команду help, или просмотрите документацию OpenOCD. Важно, чтобы OpenOCD сообщила о допустимом состоянии целевого процессора (target) и значение cpsr; если Вы увидите эти данные, то обмен через JTAG скорее всего работает правильно.
Теперь можно загрузить и запустить программу микроконтроллера. Скопируйте Ваши изменения определения JTAG в файл openocd.cfg подобно тому, как это сделано в файле openocd_flash.cfg. Замечание: есть одно малозаметное отличие между этими двумя файлами: команда target в файле openocd.cfg использует run_and_halt, в то время как в openocd_flash.cfg это run_and_init.
Команда make flash будет делать почти то же самое, как вышеприведенная сессия telnet, но вместо использования telnet система OpenOCD получает команды из дополнительного файла openocd_doflash:
wait 100
arm7_9 fast_memory_access enable# для тактовой частоты >32 кГц
flash write 0 main.bin 0x0 # запись flash
wait 10
dump_image flash.bin 0x00100000 2048 # обратное чтение первых 2 килобайт для проверки
mww 0xfffffd08 0xa5000401 # разрешение сброса пользователя
reset
shutdown
Первые команды mww (сокращение от memory write word, т. е. "запись слова в память") разрешают работу главного генератора и запускают ядро ARM на высокой частоте вместо низкой (низкая частота активируется по умолчанию после сброса или включения питания). Это повышение тактовой частоты ускоряет программирование, поскольку позволяет применить опцию fast_memory_access. После записи файла main.bin в память flash первые 2 килобайта памяти вычитываются обратно и помещаются в файл flash.bin. Утилита make сравнит этот файл с оригинальным файлом main.bin, чтобы проверить успешность программирования (как минимум для первых 2 килобайт). Последние строки вывода make flash должны выглядеть примерно так:
Замечание: Вам нужно вручную проверить результат выполнения команды cmp. Сообщение "Error 1" по той причине, что не достигнут конец файла main.bin ("EOF on main.bin" или "EOF on flash.bin"). Но если cmp сообщит "the two files differ" (два файла отличаются), то программирование не произошло.
Готово! Теперь Вы должны увидеть прямоугольные импульсы на ножке порта PA0 (pin 48) с помощью осциллографа. Если к этой ножке подключен светодиод (между +3.3V и выводом порта PA0 через токоограничительный резистор), то он будет светиться очень слабо (из-за низкой скважности импульсов тока). Для увеличения его яркости нужно изменить длительности задержек в бесконечном цикле модуля main.c.
Очистка памяти flash командой наподобие flash erase 0 0 15 не требуется, потому что команда flash write включает цикл автоочистки (auto-erase cycle). Разрешение пользовательского сброса (mww 0xfffffd08 0xa5000401) необходимо для того, чтобы могла правильно работать команда reset. Это дополнительное разрешение сброса необходимо для того, что аппаратура AT91SAM7 не делает никаких действий для включения в работу вывода NRST.
[Отладка программы]
С помощью OpenOCD можно выполнять отладку кода. Можно останавливать код выполняемой программы на целевом ARM (halt), выполнять код по шагам (single-step) и т. п. Это выполняется путем повторного открытия двух терминалов. В первом терминале запустите команду openocd -f openocd.cfg, как это было сделано при тестировании соединения через JTAG (альтернативно можно запустить команду make debug). Во втором терминале запустите отладчик (GNU debugger) командой arm-elf-gdb main.elf, и подключите его к OpenOCD выдачей команды target remote :3333, которая вводится в приглашении отладчика GDB.
Теперь можно проводить отладку способом, как это обычно делается с помощью GDB. Но тут ждет некоторая засада: точки останова (breakpoints). Точки останова обычно требуют заменить инструкцию в месте точки останова на специальную инструкцию break, чтобы отладчик мог определить момент достижения программы адреса точки останова. Однако для памяти FLASH это невозможно, поэтому Вы ограничены только двумя специальными аппаратными точками останова (hardware breakpoints), которые встроены в систему отладки процессора ARM. Чтобы решить эту проблему, выполните следующую команду GDB перед использованием любой точки останова: monitor arm7_9 force_hw_bkpts enable.
Имейте в виду, что Вы можете напрямую вызывать команды OpenOCD из GDB, снабжая их префиксом monitor. Например, команда monitor reset сбросит микроконтроллер простым коротким переводом ножки NRST в лог. 0.
Вот пример простой сессии GDB:
bash# arm-elf-gdb main.elf
GNU gdb 6.4
Copyright 2005 Free Software Foundation, Inc.
GDB is free software, covered by the GNU General Public License, and you are
welcome to change it and/or distribute copies of it under certain conditions.
Type "show copying" to see the conditions.
There is absolutely no warranty for GDB. Type "show warranty" for details.
This GDB was configured as "--host=i686-pc-linux-gnu --target=arm-elf"...
(gdb) target remote :3333
Remote debugging using :3333
main () at main.c:166
166 for(i=0; i < 10; i++) nop();
(gdb) monitor arm7_9 force_hw_bkpts enable
force hardware breakpoints enabled
(gdb) print i
$1 = 7
(gdb) bt
#0 main () at main.c:166
(gdb) info regi
r0 0xfffff400 -3072
[...]
r12 0x1 1
sp 0x203fe4 2113508
lr 0x22c 556
pc 0x2e0 736
fps 0x0 0
cpsr 0x800000d7 -2147483433
(gdb) cont
Continuing.
[^C pressed]
Program received signal SIGINT, Interrupt.
0x000002e8 in main () at main.c:166
166 for(i=0; i < 10; i++) nop();
(gdb) kill
Kill the program being debugged? (y or n) y
Многое здесь взято из замечательной статьи [10] (автор James P. Lynch), она хорошо поможет пользователям Windows.