Решил заняться чем-то новым, точнее хорошо забытым старым, и освоить форт на микроконтроллерах.
Перед компилируемыми ЯП форт имеет преимущество за счёт своей интерактивности - писать и отлаживать код можно непосредственно на целевом устройстве не занимаясь каждый раз сборкой и прошивкой.
С другой стороны, микроконтроллеры уже достаточно мощные, что бы запускать интерпретируемые языки, такие как Lua или JavaScript, но они отбирают для своей работы ценные ресурсы МК, да и работают сравнительно медленно.
Ядро же форт-машин компактно, и сам язык позволяет генерировать эффективный код, что позволяет на минимальном железе писать программы сопоставимые по скорости с ассемблерными, при этом обладающие хорошей переносимостью между различными архитектурами (в рамках одной реализации).
Из-за простоты реализации существует огромное множество форт-машин, и выбор какой-то конкретной - дело не простое. При должном опыте можно даже свою написать ;-)
Поковыряв имеющиеся предложения я пока остановился на Mecrisp Forth изначально созданном для архитектуры MSP430, в дальнейшем он был портирован на ARM Cortex M микроконтроллеры под именем Mecrisp-Stellaris. Не смотря на явное указание на конкретное семейство МК от конкретного производителя список поддерживаемых МК гораздо шире.
Среди поддерживаемых МК и пплат разработки оказалась и Nucleo-F401RE, а их у меня как раз парочка завалялась. На ней и буду ставить эксперименты.
Установка Mecrisp-Stellaris
Качаем свежую версию Mecrisp-stellaris. Потребуется именно версия stellaris
для МК на Cortex M ядрах.
Распаковываем архив в удобное место, и среди предложенного обилия процессоров входим в каталог stm32f401
.
Нам потребуется прошить бинарный файл с прошивкой mecrisp-stellaris-stm32f401.bin
в микроконтроллер.
Для этого воспользуемся утилитой st-flash
из Open source ST Link tools.
Прежде всего, нужно полностью стереть микроконтроллер. Это обязательный шаг и необходим для очистки совести флеш-памяти МК, иначе Mecrisp подхватит оставшейся в ней мусор, и завалит вас ошибками.
st-flash erase
Следующим шагом, загружаем прошивку в МК:
st-flash write mecrisp-stellaris-stm32f401.bin 0x08000000
Или можно загрузить её же из hex файла:
st-flash --format ihex write mecrisp-stellaris-stm32f401.hex
В этом случае, не требуется указывать адрес памяти, куда будет прошивка загружена.
Если загрузка прошла без ошибок, то теперь у нас в МК крутится форт-машина.
Консоль
Пока нет связи с форт-машиной, она представляет собой не просто чёрный ящик, а чёрный ящик с наглухо заваренными входами и выходами. Поэтому потребуется терминал умеющий взаимодействовать с последовательным портом.
В том же каталоге, что и прошивка есть шелл-скрипт terminal
смысл жизни
которого заключается в запуске консольного терминала picocom
.
Но для запуска picocom его сперва надо установить. В deb-based системах это делается командой:
apt-get install picocom minicom
Обратите внимание, что я кроме picocom ещё устанавливают minicom. Это более
сложный терминал последовательного порта, но сейчас нам от него нужно
только консольное приложение ascii-xfr
которое позволит передавать
файлы по последовательному порту.
Теперь можно запустить терминал ./terminal
, что бы подключиться к форт-машине в МК.
На самом деле в терминале мы ничего не увидим, машина совершенно не в курсе, что к ней подключились.
Так что начнём с начала и сбросим МК, либо аппаратной кнопкой RESET
на плате,
либо набрав в терминале reset
и нажав <enter>
.
После этого, в терминале выскочит информация о версии форт-машины:
Mecrisp-Stellaris 2.3.9 for STM32F401 by Matthias Koch
Впрочем, никто не мешает воспользоваться любым привычным терминалом, хоть консольным, хоть графическим. Но я рекомендую обратить внимание на такие, которые позволяют отредактировать строку для отправки в отдельном поле ввода и отправить её в терминал всю целиком, а не посимвольно. Ибо протокол обмена по последовательному порту у Mecrips не предполагает обширных возможностей для редактирования: максимум - стереть неправильно введённые символы.
Так же хорошей идеей будет воспользоваться специализированным терминалом Folie, который был написан специально для взаимодействия с Mecrisp Forth.
Хоть он и выглядит так же просто, как picocom, но для ввода команд использует библиотеку readline
, что позволяет
удобно редактировать вводимую строку, а так же пользоваться историей команд.
“Я у мамы программист”
Теперь попробуем сделать что-нибудь простое. Например посчитать известное 2+2*2
.
На форте это может выглядеть так:
2 2 2 * + .
Жмём <enter>
и получаем ответ:
6 ok.
Если перевести, то что мы проделали, то получится следующая последовательность:
- Поместить в стек число
2
. - Поместить в стек число
2
. - Поместить в стек число
2
. - Извлечь из стека два числа, перемножить их и поместить результат в стек.
- Извлечь из стека два числа, сложить их и поместить результат в стек.
- Извлечь из стека число и напечатать его.
Да, никаких алгебраических записей арифметических действий. Только простые команды, только хардкор.
Изучить основы форта за Y минут, можно на соответствующей странице.
Список ключевых слов доступных в ядре Mecrisp forth
можно найти на странице http://mecrisp.sourceforge.net/glossary.htm .
Запускаем пример
В каталоге с бинарными файлами лежит пример взаимодействия с железом МК - blinky.txt
.
В нём простейшая программа на форте. Точнее даже не программа, а набор определений
слов форта и констант.
Что бы загрузить эти определения в МК, нам достаточно отправить этот файл “как есть” по последовательному порту с паузой между строками порядка 50мс (если слать все строки без пауз, форт не успеет их все обработать и мы получим эпик фейл).
Если мы подключались через скрипт terminal
, то для отправки файла нужно нажать последовательно
Ctrl+A
, Ctrl+S
и на запрос ввести имя файла.
Но сейчас подключимся через folie
:
folie -p /dev/ttyACM0 -t 50ms
Ключ -p
указывает последовательный порт, к которому подключена форт-машина,
а ключ -t
позволяет сэмулировать паузу между строками файла. По умолчанию она 500мс, и
файл будет грузится ну очень медленно.
Для загрузки файлов folie предоставляет встроенную команду include <filename>
.
Указывать её надо с самого начала строки, без пробелов перед ней. Т.е. если в вашей форт-машине
определено слово include
вызвать его можно поставив перед ним пробел. Для форта это корректный синтаксис,
а folie не распознает её как свою команду, и передаст строку напрямую в порт.
include blinky.txt
В результате увидим следующий вывод:
\ >>> include blinky.txt
\ <<<<<<<<<<< blinky.txt (68 lines)
\ done.
И теперь можем исполнить слово blinky
для запуска очень полезной мигалки
светодиодом.
Я позволил себе вольность и вычистил пример от неиспользуемого кода и снабдил комментариями:
\ LED and button example
\ Базовые адреса GPIO МК
\ Константы задаются с помощью ключевого слова constant
$40020000 constant PORTA_Base \ Шестнадцатиричные числа начинаются с $
$40020800 constant PORTC_Base
\ Регистры GPIO МК
PORTA_BASE $00 + constant PORTA_MODER \ Reset 0 Port Mode Register - 00=Input 01=Output 10=Alternate 11=Analog
PORTA_BASE $18 + constant PORTA_BSRR \ WO Bit set/reset register 31:16 Reset 15:0 Set
\ +$1C ... is Lock Register, unused
PORTC_BASE $10 + constant PORTC_IDR \ RO Input Data Register
\ User LD2: the green LED is a user LED connected to Arduino signal D13 corresponding to
\ MCU I/O PA5 (pin 21) or PB13 (pin 34) depending on the STM32 target.
\ On the Nucleo 401RE, this is PA5.
\ B1 USER: the user button is connected to the I/O PC13 (pin 2) of the STM32
\ microcontroller.
\ Однострочные комментарии начинаются с обратного слеша.
\ Так же комментарии можно указывать в круглых скобках.
\ Обычно они используются для описания стековых диаграмм слов форта.
: button? ( -- ? ) \ Считываем значение кнопки
1 13 lshift \ Эквивалентно (1<<13)
PORTC_IDR bit@ \ Считываем бит из адреса памяти (регистра МК)
not ; \ Инвертируем результат
: led ( ? -- )
if
1 5 lshift \ Эквивалетно (1<<5)
PORTA_BSRR ! \ Записываем значение в регистр МК. Это установит бит порта
else
1 5 lshift 16 lshift \ Эквивалетно ((1<<5)<<16)
PORTA_BSRR ! \ Записываем значение в регистр МК. Это сбросит бит порта
then ;
: led_setup ( -- )
%01 \ Двоичное число в стек
5 2* \ 5*2 в стек
lshift \ Битовый сдвиг
PORTA_MODER bis! ; \ Устанавливаем биты в ячейке памяти (регистре МК)
: led_blink ( delay -- )
true led \ Включаем светодиод. Слово true помещает на стек значение -1, что соответствует истине.
dup 0 do loop \ Дублируем значение задержки и запускаем пустой цикл от 0 до значения задержки
false led \ Выключаем светодиод. Слово false помещает на стек значение 0, что соответствует лжи.
0 do loop ; \ На вершине стека осталось значение задержки, запускаем пустой цикл с ним.
: blinky ( -- )
led_setup \ Настраиваем GPIO на выход.
begin \ Начинаем условный цикл
\ Проверяем состояние аппаратной кнопки
button? if
\ Если нажата - мигаем быстро
500000 led_blink
else
\ Если отпущена - медленно
1000000 led_blink
then
key? until \ Если есть данные с терминала (key? вернула -1), то завершаем цикл.
;
Сохранение определений слов во flash
Определим новое слово для возведения числа в квадрат:
: sqr ( ? -- ? ) dup * ;
Теперь мы можем возводить числа в квадрат:
5 sqr .
25 ok.
Но если использовать аппаратный сброс МК:
reset
Магия перестаёт работать:
5 sqr .
sqr not found.
Дело в том, что по умолчанию слова компилируются в оперативную память и это правильно, что бы не загрязнять флеш не отлаженными словами.
Переключить компиляцию в режим сохранения во flash можно словом compiletoflash
,
а обратно словом compiletoram
.
compiletoflash
: sqr dup * ;
compiletoram
: sqr2 sqr 2* ;
Теперь после сброса МК слово sqr
будет доступно, а слово sqr2
- нет.
Толмач с машинного
Среди файлов поставки Mecrisp forth присутствуют файлы
common/disassembler-m0.txt
и
common/disassembler-m3.txt
,
что есть ни что иное, как дизассемблер для соответствующих архитектур.
Загрузим его:
include ../common/disassembler-m3.txt
О ужас, целых 474 строки (m0 поменьше - 316).
\ >>> include ../common/disassembler-m3.txt
\ <<<<<<<<<<< ../common/disassembler-m3.txt (474 lines)
\ done.
И для эксперимента определим новое слово:
: delay ( ? -- ) 0 do loop ;
Теперь его можно дизассемблировать.
see delay
Результат исполнения:
20002858: B500 push { lr }
2000285A: B430 push { r4 r5 }
2000285C: 2400 movs r4 #0
2000285E: 0035 lsls r5 r6 #0
20002860: CF40 ldmia r7 { r6 }
20002862: 3401 adds r4 #1
20002864: 42AC cmp r4 r5
20002866: D1FC bne 20002862
20002868: BC30 pop { r4 r5 }
2000286A: BD00 pop { pc }
Можно переписать определение слова по другому
: delay1 ( ? -- ) begin 1- dup until ;
выиграв три команды:
see delay1
2000297A: B500 push { lr }
2000297C: 3E01 subs r6 #1
2000297E: F847 str r6 [ r7 #-4 ]!
20002980: 6D04
20002982: 2E00 cmp r6 #0
20002984: CF40 ldmia r7 { r6 }
20002986: D0F9 beq 2000297C
20002988: BD00 pop { pc }
Библиотеки
Конечно можно медленно и печально нарабатывать свою библиотеку с нуля, но гораздо оптимальнее воспользоваться уже готовыми наработками.
Во-первых, рекомендую ознакомиться с каталогом common
в поставке
Mecrisp-stellaris. Там много вкусного.
Опять же, вкусняшки можно найти в каталоге конкретного процессора.
Коллекция кода от JeeLabs https://github.com/jeelabs/embello/tree/master/explore/1608-forth
Неофициальная документация по Mecrisp Stellaris Forth.
comments powered by Disqus