Первое что приходит на ум это создать 2х приложение, один из потоков которого отвечает за интерфейс отладки, другой за программу пользователя, что я и сделал. Переключение между потоками осуществляется по таймеру, каждый поток имеет свой стек. Кучу я решил не использовать для написания интерфейса отладки т.к. их необходимо использовать 2 разных, либо при работе с кучей постоянно переключаться на один поток.
Первая идея для реализации по инструкционной отладки, была сократить время между прерываниями таймера ровно настолько чтоб могла исполниться только 1 инструкция. Данный вариант показал свою идеальную работу на микроконтроллере Atmega328p, дело в том, что минимальное время между прерываниями для Atmega составляет 1 такт процессора, любая инструкция независимо от количества тактов нужного для ее выполнения всегда завершиться если ее выполнение началось.
К сожалению, когда я перешел на stm32, такой вариант не сработал, потому что ядро Cortex-M может прервать исполнение инструкции на середине, а затем по возращению из прерывания начать исполнять ее заново. Тогда я решил хорошо нет проблем просто буду увеличивать количество тактов между прерываниями пока инструкция не исполниться, тут я наткнулся на еще одну проблему некоторые инструкции исполняются только со следующий за ними инструкцией или не исполняются вообще, к сожалению данная проблема не решаемая, поэтому я решил в корне изменить подход.
Я решил эмулировать эти инструкции. На первый взгляд идея написать эмулятор ядра Cortex M, да еще который бы помещался в память микроконтроллера выглядит фантастично. Но я понял, что мне не нужно эмулироваться все инструкции, а только те из них, которые связанны с программным счетчиком, таких оказалось не так уж много. Все остальные я могу просто переместить в другой участок памяти и исполнить там, а чтоб исполнилась только одна инструкция, добавим после нее инструкцию прыжка на себя “b”.
И тут пришла очередь точек останова, для их реализации я решил использовать инструкцию реализующую логику while(1);. Заменяем инструкцию, которая находиться по адресу где хотим поставить точку останова и ждем прерывания таймера. Понимаю, что что-нибудь вроде инструкции, которая вызовет исключение было бы лучше, но хотелось сделать универсальный вариант. При исполнении заменяем данную инструкцию обратно. Хороший вариант если, исполнять программу в оперативной памяти микроконтроллера, в противном случае flash память микроконтроллера долго не проживет. Но к этому моменту я уже закончил писать эмулятор инструкций для stm32 и решил почему бы не написать такой же для Atmega328 и написал. Теперь инструкции заменять обратно не надо их можно эмулировать.
Для того чтоб подружить все это со средой исполнения я сначала хотел, написать свой gdb клиент. К сожалению, он поддерживает два интерфейса для работы с ide. Каким пользоваться ide решает сама. Реализовывать их обоих (первый мне показался довольно простым, второй не очень), плюс пришлось бы совмещать исходники с прошивкой что мне показалось не очень хорошей идеей. Поэтому я решил написать свой gdbserver к счастью протокол был один и он был довольно простой.
Первым моим интерфейсом, который я решил реализовать был GSM. В качестве приемо-передатчика я решил использовать SIM800, в качестве сервера, сервер с поддержкой php. Основная идея заключалась в том, чтоб после прихода post запроса от микроконтроллера держать соединение с микроконтроллером в течении 30 секунд и каждые 100 мс обращаться к базе, если данные появились отсылать их в качестве ответа на запрос и ждать следующего запроса от микроконтроллера.
Первое подключение gdb клиента к серверу, показало, что запросов от gdb клиента серверу слишком много при команде pause или step. Поэтому было решено объединить все эти запросы в один большой для микроконтроллера, для этого я понял логику этих запросов и научился их предсказывать. Теперь данные команды исполнялись не так чтобы быстро, хотелось бы быстрее, но терпимо.
Следующим интерфейсом был usb, для микроконтроллера Atmega328 я решил использовать библиотеку V-usb. Для работы с usb я переписал логику команды run. Теперь микроконтроллер после данной команды не запускал программу ожидая команды pause, он запускал ее на 1 секунду затем на него отправлялась новая команда run и.т.д. Такая логика необходима т.к. я отключаю интерфейс во время работы программы. Для избавления себя от написания драйвера, я решил использовать стандартный hid драйвер. В качестве функций общения hid get feature report, hid set feature report.
Что касается перепрошивки микроконтроллеров, то я решил, что для интерфейса usb это лишнее, так для первой прошивки вам все равно понадобиться программатор. А вот для интерфейса GSM самое оно. Обычно для этой цели пишут отдельную программу, но я решил поступить по-другому, за место написания отдельной программы, я решил загружать программу во flash память микроконтроллера полностью, затем после завершения загрузки копировать данную программу в начало памяти. Затем я подумал зачем мне пересылать программу целиком, я могу переслать только разницу между текущим и предыдущим бинарным файлом прошивки.
Чтоб минимизировать данную разницу, я решил для части программы пользователя переименовать секции .text, .data, .bss, .contructors array(обобщенное название у разных микроконтроллеров разное) и размещать их в памяти сразу после основной программы.
Пришлось также написать свои функции для инициализации данных секций. Теперь в большинстве случаев маленькое изменение программы равно маленькому изменению бинарного файла равно маленькому количеству пересылаемых данных. Как результат часто микроконтроллер перепрошивается быстрее чем работают команды RUN, STEP, PAUSE.
Ну и напоследок видео работы:
Stm32 отладка через usb интерфейс.
Stm32 отладка через gsm интерфейс.
Atmega328 отладка через usb интерфейс.
Atmega328 отладка через gsm интерфейс.
ссылка на оригинал статьи https://habr.com/ru/post/512784/
Добавить комментарий