GPIO
Возьмем строку 58 в исходном файле hello.c нашего проекта Blinky и посмотрим, сможем ли мы придумать краткий фрагмент кода в качестве замены. Это функция, в которую мы углубились (дважды):
metal_gpio_enable_output(led0, 5);
Что ж, поскольку мы знаем, что хотим записать логическое значение 1 для адреса 0x10012008, давайте сделаем именно это. Удалив точку прерывания из строки 58 и закомментировав эту строку, мы можем использовать следующую строку для замены:
*((uint32_t *) 0x10012008) |= 0x20;
Вот как это выглядит после добавления кода замены и точки прерывания:
Давайте попробуем это сделать. Вот отладчик непосредственно перед выполнением этой строки:
На этот раз обратите внимание на разборку. Все, что для этого требуется, - это 7 инструкций по сборке. Ну, первые две команды (lui и addi) дублируются после инструкции lw без видимой причины (посмотрите на команды по адресам, заканчивающимся на 0xf2 и 0xf4). Даже с этими двумя дублированными командами это намного быстрее, чем его аналог из библиотеки Freedom Metal, для которого в финальном задании потребовалось 8 команд, плюс вся работа, которую необходимо выполнить, прежде чем перейти к этому этапу.
Возобновление выполнения приведет к тому, что светодиод будет мигать, как и раньше.
Изменение уровня оптимизации
Теперь о дублированных командах в сборке:
Дублированные команды представляют собой пару lui и addi по адресам, заканчивающимся на 0xf4 и 0xf8. Эти 2 команды избыточны, потому что все, что они делают, - это записывают адрес регистра output_en в a5. В первый раз это делается для того, чтобы команда lw извлекла значение, на которое указывает a5 (output_en). Однако второй раз, когда эти две команды появляются в коде, должно быть, это был первый шаг реализации операции записи, так что команда sw в конце этой последовательности имеет адрес output_en.
Это, несомненно, проблема оптимизации, из-за которой компилятор пропустил удаление избыточного ассемблерного кода. Чтобы повысить уровень оптимизации, вы можете открыть debug.mk или release.mk файлы в папке src слева, в зависимости от конфигурации, с которой вы создаете. В нашем случае давайте откроем ./src/debug.mk:
Как вы можете видеть, уровень оптимизации равен -O0. Измените это значение на -O1 во всех 3 флагах, сохраните файл и перестройте его заново. Вот результирующий ассемблерный код:
Вот это уже больше похоже на правду! Всего 4 команды для записи одного бита.
Если вы продолжаете и хотите убедить себя, что эта строка замены в исходном коде C действительно эффективна, попробуйте закомментировать ее так, чтобы там не было ни строки 58, ни строки 60. Когда вы запустите свое приложение, вы увидите, что светодиод не будет мигать. это потому, что выходной буфер никогда не был включен.
Написание наших собственных функций ввода-вывода
Давайте оглянемся назад на нашу однострочную альтернативную реализацию metal_gpio_enable_output(led0, 5):
*((uint32_t *) 0x10012008) |= 0x20;
Да, он функционален, и он преобразует его в машинный код за 4 команды. Однако это немного похоже на программирование "с ножом в лесу". Он просто слишком строгий, непереносимый и не очень удобочитаемый. Давайте посмотрим, сможем ли мы исправить какой-либо из этих 3 недостатков.
Вот что мы можем сделать с помощью макрофункции:
#define Red_V_enable_output(x) *((uint32_t *) 0x10012008) |= (1<<(x))
Хотя с помощью этого макроса будет сгенерирован тот же ассемблерный код, он немного лучше:
Он более гибкий.
Макрос может сконфигурировать любой вывод GPIO с аргументом x.
Он более удобочитаем.
Очевидно Red_V_enable_output(5) скажет больше чем *((uint32_t *) 0x10012008) |= 0x20.
Переносимость по-прежнему ограничена.
В этом макросе по-прежнему отсутствуют база и смещение для изменения регистра. Все это можно сделать во время компиляции с большим количеством макросов и большим терпением. Поскольку в микроконтроллере FE310 существует только одно устройство GPIO, этот макрос по-прежнему довольно хорош.
Стоит ли писать наши собственные функции?
Одним из аргументов в пользу ответа "Нет" является то, что функции конфигурации, скорее всего, будут вызваны только один раз, прежде чем мы войдем в основной цикл. В этом случае накладные расходы незначительны.
Однако внутри основного цикла используются другие функции, которые могут препятствовать достижению приемлемой скорости выполнения.
Возьмем, к примеру, функцию metal_gpio_set_pin(). Эта функция используется для включения и выключения светодиода, но она проходит через цепочку функций, очень похожих на то, что мы видели для metal_gpio_enable_output().
Конечно, в нашем примере задержка между вызовами metal_gpio_set_pin() составляет 500 мс. Это занимает полсекунды, поэтому использование макроса, который выполняет задание в 4 командах, не помогло бы в этом конкретном приложении. Однако, возможно, было бы хорошей идеей написать для этого макрос. На самом деле, мы напишем 2 макроса: один для установки пина (записав в него 1) и один для очистки пина (записав в него 0).
Для этого нам нужно смещение для регистра output_val, которое находится в следующей таблице, которую мы видели ранее:
![Смещения и описания регистров конфигурации GPIO (Взято с руководства пользователя FE310-G002, размещено с разрешения SiFive, Inc.)](/EDI/20_01_25_2/1737325222-1077/tutorial/1372/objects/4/files/03-24.jpg)
Рис. 3.24. Смещения и описания регистров конфигурации GPIO (Взято с руководства пользователя FE310-G002, размещено с разрешения SiFive, Inc.)
Теперь мы можем записывать функции таким образом
#define Red_V_set_pin(x) *((uint32_t *) 0x1001200C) |= (1<<(x)) #define Red_V_clear_pin(x) *((uint32_t *) 0x1001200C) &= ~(1<<(x))
Как вы, возможно, знаете, установка битов достигается с помощью побитовой операции ИЛИ с маской с единицами в битах, которые мы хотим установить, тогда как очистка битов достигается с помощью побитовой операции И с перевернутой маской.
Заменяя вызовы функций, основной цикл выглядит следующим образом:
Это новое приложение работает точно так же, как и исходное приложение. Помните, что макросы работают намного быстрее, чем функции библиотеки Freedom Metal.
Итак, чтобы ответить на вопрос: да, написание ваших собственных функций или макросов может быть хорошей идеей. Просто убедитесь, что вам нужна альтернатива, и внимательно прочитайте реализацию библиотеки, чтобы убедиться, что вы выполнили все необходимые проверки безопасности.