Захотелось мне более читаемого и интуитивного ассемблера. Сейчас более менее работает версия под stm8s (наполняю библиотеку макросов) собственно транслятор уже знает и stm8l и avr - но под них нет пока никаких заголовков - вопрос времени.
Начнем с примера: вот так выглядит примитивный "блинк" на псевдо ассемблерами с макросами:
include stm8s207x8
include system
include gpio
define led 5
interrupt reset@0x8000: (f32) {0x82000000+start}
delay: x = 65000; dloop: x--; if z=0 dloop; ret
start: Stack.init(ramend); Gpiod.output(led); Gpiod.pushup(led)
loop: call delay; Gpiod.invert(led); go near loop
То же самое без всяких там макросов:
include stm8s207x8
interrupt reset@0x8000: (f32) {0x82000000+start}
delay: x = 65000; dloop: x--; if z=0 dloop; ret
start: x = ramend; sp = x; gpiod ddr#5 = 1; gpiod acr1#5 = 1
loop: call delay; !gpiod odr#5; go near loop
Генерится нативный код (вырезка, там сверху еще преамбула):
flash segment byte at: 8000 'flash'
interruptreset:
dc.l {$82000000+start}
delay: ldw x,#65000
dloop: decw x
jrne dloop
ret
start: ldw x,#$17ff
ldw sp,x
bset gpiodddr,#5
bset gpiodcr1,#5
loop: call delay
bcpl gpiododr,#5
jra loop
В этом языке псевдо-ассемблера есть очень мало команд (36 против 81 у stm8), однако каждая команда превращается ровно в одну ассемблерную инструкцию. Нельзя в одной команде сочетать несколько действий - это ассемблер! Команды разделяются символами ";" или "\n". Можно писать несколько команд в одной строке через ';'. После метки ';'ставить не нужно, там есть ':' и этого достаточно. Нельзя переносить полкоманды на другую строку (например if на одной строке а его метка на другой). Необходимо конечно помнить и об ограничениях в режимах адресации команд нативного ассемблера - они само собой присутствуют и транслятор никак от этого не спасает.
Подробности установки в конце файла этого файла.
- КОМЕНТАРИЙ - // текст, можно ставить как в начале так и после любой команды кроме 'define' препроцессора
- ПРИСВАИВАНИЕ - X = Y ( под присваиванием подразумевается и обнуление, и установка/сброс флага и т.п.)
- СЛОЖЕНИЕ - X += Y
- ВЫЧИТАНИЕ - X -= Y
- УМНОЖЕНИЕ - *X = Y ( в версии stm8 реально доступны только x *= a или y *= a)
- ДЕЛЕНИЕ - X /= Y ( в версии stm8 реально доступны только x/=a, y/=a или x/=y - последний меняет и x и y - в y возвращает остаток от деления )
- ИНВЕРСИЯ ЗНАКА - -X
- ПРОВЕРКА ФЛАГОВ - ?X
- СРАВНЕНИЕ X ? Y ( в версии stm8 реально доступны только a?... x?... y?...)
- ИНКРЕМЕНТ - X++ или ++X (два варианта полностью эквивалентны)
- ДЕКРЕМЕНТ - X-- или --X (два варианта полностью эквивалентны)
- ПОБИТОВОЕ AND - X &= Y
- ПОБИТОВОЕ OR - X |= Y
- ПОБИТОВОЕ XOR - X ^= Y
- ПОБИТОВОЕ NOT - !X
- ПОБИТОВОЕ СРАВНЕНИЕ - X &? Y ( в версии stm8 реально доступны только a&?)
- КОЛЬЦЕВОЙ СДВИГ ЧЕРЕЗ С - c<X<c
- КОЛЬЦЕВОЙ СДВИГ ЧЕРЕЗ С - c>X>c
- СДВИГ УМНОЖЕНИЕ НА 2 - c<X<0
- СДВИГ ЗНАКОВОЕ ДЕЛЕНИЕ НА 2 - s>X>c
- СДВИГ БЕЗЗНАКОВОЕ ДЕЛЕНИЕ НА 2 - 0>X>c
- КОЛЬЦЕВОЙ СДВИГ ЧЕРЕЗ A - a<X<a ( X только x или y)
- КОЛЬЦЕВОЙ СДВИГ ЧЕРЕЗ A - a>X>a ( X только x или y)
- ВЫЗОВ ФУНКЦИИ CALL - call near X или call X или call far X
- ВОЗВРАТ ИЗ ФУНКЦИИ - ret или ret far
- ВОЗВРАТ ИЗ ПРЕРЫВАНИЯ - iret
- ПЕРЕХОД БЕЗУСЛОВНЫЙ - go near X или go X или go far X
- ПЕРЕХОД УСЛОВНЫЙ - if condition X
- ПРОПУСК УСЛОВНЫЙ - skip X#Y=Z (в avr)
- ОСТАНОВ - halt
- ОСТАНОВ С ОЖИДАНИЕМ СОБЫТИЯ - wait
- ОСТАНОВ С ОЖИДАНИЕМ ПРЕРЫВАНИЯ - wait i
- ПРОГРАММНОЕ ПРЕРЫВАНИЕ - trap
- НИЧЕГО НЕ ДЕЛАНИЕ nop или break
- ЖДАТЬ ПОКА БИТ НЕ СТАНЕТ ?->
Комбинированные операции - генерируют несколько команд:
- ПЕРЕХОД УСЛОВНЫЙ - if чтото=0 X или if чтото<>0 X где что то - регистр или переменная
Вставка нативных операций процессора:
- ВСТАВКА НАТИВНОЙ КОМАНДЫ inline cmd X,Y; (внутри inline строки только одна строка ассемблера, но в ней можно использовать препроцессор нативного ассемблера это дает дополнительную свободу при составлении своих макросов)
- НЕПОСРЕДСТВЕННЫЙ - 123 - число или там где уместно 'c' - символ
- РЕГИСТРОВЫЙ - r - имя регистра. Может быть a,cc,x,xl,xh,y,yl,yh,sp результат - значение регистра
- ОТНОСИТЕЛЬНЫЙ - label - относительный адрес (метка при адресации)
- ПРЯМОЙ - label - результат - значение переменной по адресу данной метки
- ПОРТ ВВ - - результат - значение порта (это для avr)
- ИНДЕКСНЫЙ - [r] - имя регистра может быть x,y,sp результат - значение переменной по адресу из регистра
- ИНДЕКСНЫЙ - label[r] - имя регистра может быть x,y,sp результат - значение переменной по адресу label со смещением из r
- НЕПРЯМОЙ - [label] - значение переменной из расположенной по адресу из ячейки памяти расположенной по адресу хранящейся в ячейке памяти
- НЕПРЯМОЙ - [label[r]] - имя регистра может быть x,y,sp результат - значение переменной из расположенной по адресу из ячейки памяти расположенной по адресу label со смещением из r
- ПОБИТОВЫЙ - label#bit - результат - значение бита по адресу данной метки с номером bit
- ПОБИТОВЫЙ К ПОРТУ ВВ - #bit - результат - значение бита по адресу данной метки с номером bit (для avr)
- МЕТКА С АДРЕСОМ - label@address:
- МЕТКА - label:
- ПРИВЯЗАННЫЕ ПЕРЕМЕННЫЕ - (u8), (u16), (u32) - нужны для привязывания имени к какому либо месту, используются например для описания регистров ввода вывода
- НЕИНИЦИАЛИЗИРОВАННЫЕ ПЕРЕМЕННЫЕ - (x8), (x16), (x32) где x = {r,m} r - псевдорегистровая память, m - оперативная память
- ИНИЦИАЛИЗИРОВАННЫЕ ЗНАЧЕНИЯ - (x8) {val}, (x16) {val}, (x32) {val} где x = {f,e} f - флэш память, e - eeprom
- ПРИВЯЗАННЫЕ МАССИВЫ - (u8)[n], (u16)[n], (u32)[n] где n - размерность массива
- НЕИНИЦИАЛИЗИРОВАННЫЕ МАССИВЫ - (x8)[n], (x16)[n], (x32)[n] где x = {r,m} r - псевдорегистровая память, m - оперативная память, n - размерность массива
- ИНИЦИАЛИЗИРОВАННЫЕ МАССИВЫ - (x8)[n] {vals}, (x16)[n]{vals}, (x32)[n]{vals}, (x8)[n]"line" где x = {f,e} f - флэш память, e - eeprom, vals,line - данные для инициализации массива
- ПОДКЛЮЧИТЬ ФАЙЛ - include filename{.ext}
- ОПРЕДЕЛИТЬ КОНСТАНТУ - define const{ value}
- ОПРЕДЕЛИТЬ МАКРОС - define name(parameter1{,parameter2...}) macros_body
В макросе можно использовать несколько строк:
define a(b) a=b;\ // обязательно завершайте команды в обрезанных строках символом ';'
b++;\
c=a;
В макросе можно использовать локальные метки:
define a(b) @1: b--; if z=0 @1;
- УСЛОВНАЯ КОМПИЛЯЦИЯ - ifdef const ... endif или ifndef const ... endif
Препроцессор примитивный, однопроходный и тупо подменяющий строки. Будьте осторожнее. В именах констант и макросов можно использовать английские буквы, цифры и символ точки.
Ассемблер экспериментальный, поэтому хотелось сделать программу-транслятор максимально простой и легко модифициуемой, поскольку сам окончательный язык в начале не был ясен, а рождался по ходу написания транслятора. За простоту транслятора приходится платить.
При компиляции из всего кода удаляются все пробелы (исключения ниже). Это дает возможность вставлять пробелы для удобочитаемости внутрь переменных и даже чисел, апример 'gpioa odr#0 = 1' и 'if z = 0 loop'. НО убираются абсолютно все пробелы, в том числе из строк инициализации данных ( " тут все пробелы будут удалены")! Решение:
line: (f8)[3] {"тут"}; (f8) {32}; (f8)[6] {"строка"}; (а8) {0}
Да, не элегантно, но так ли часто мы используем строки на крохотных контроллерах? Обещанные исключения: нельзя разбавлять пробелами инструкции препроцессора - он работает до компилятора, до "сжатия" пробелов. И не жмутся пробелы при вставке команд нативного ассемблера: inline elpm X; -> 'elpm X'.
Еще одна неприятная неявность заключается в разбиении строк на "команды" по ';'. То есть строка
(f8)[27]"бедная строка; ее разрежут",
будет распилена на две непонятных команды
(f8)[27]"беднаястрока и ееразрежут".
Решение аналогично борьбе с пробелами.
Сначала чуть чуть о stm8 после reset:
- тактировние от внутреннего генератора (hsi) с множителем 8 - частота fmaster = 2 МГц
- тактирование всех периферийных устройств включено
- прерывания глобально запрещены
Итак...
- Stack.init(param) - инициализирует указатель стека в значение 'param'
- Interrupt.disable - общий запрет прерываний
- Interrupt.enable - общее разрешение прерываний
Прерывния (я обернул столь простые команды - i=0 и i=1 в макрос потому что интуитивно не понятно что 1 это запрет, а 0 разрешение)
Дам имена макросов для Gpioa - для остальных аналогично
-
Gpioa.output(pin) - настроить ногу pin как выход
-
Gpioa.pushup(pin) - подключить в pin резистор подтягивающий ногу к питанию
-
Gpioa.float(pin) - отключить в pin резистор подтягивающий ногу к питанию
-
Gpioa.fast(pin) - включить в pin режим при котором скорость реакции выше но и потребление выше
-
Gpioa.slow(pin) - включить в pin режим при котором скорость реакции ниже но и потребление меньше
-
Gpioa.set(pin) - выставить на ноге pin значение "1"
-
Gpioa.clr(pin) - выставить на ноге pin значение "0"
-
Gpioa.invert(pin) - инверсия значения на ноге pin
-
Gpioa.setvalue(value) - присвоить порту значение
-
Gpioa.input(pin) - настроить ногу pin как вход
-
Gpioa.interrupt(pin) - разрешить прерывания по ноге pin
-
Gpioa.disableinterrupt(pin) - запретить прерывания по ноге pin
-
Gpioa.getvalue - прочитать значение из порта
-
Gpioa.get(pin) - прочитать значение на ноге pin
- Power.on.all - включить тактирование всех устройств
- Power.off.all - выключить тактирование всех устройств
- Power.on.имяустройства - включить тактирование устройства с указанным именем
- Power.off.имяустройства - выключить тактирование устройства с указанным именем
Допустимые имена устройств: tim1,tim2,tim3,tim4,uart1,uart2,spi,i2c,awu,adc
- Flash.unprotect - разрешить запись в область flash
- Flash.protect - запретить запись во flash
- Eeprom.unprotect - разрешить запись в область eeprom
- Eeprom.protect - запретить запись во eeprom
Оба uart есть только в 20х серии stm8s в более младших есть либо uart1 либо uart2. Чтобы улучшить переносимость кода, между младшими контроллерами вместо Uart{X} можно использовать префикс Serial. Serial автоматически заменяется на имеющийся в данном контроллере uart. В контроллерах 20x серии Serial - псевдоним uart с младшим номером из имеющихся. Макросы даю для Uart1, для Uart2 они такие-же. Это простая небуферизированная передача без прерываний и т.п.
- Uart1.init(fmaster,speed) - инициализация uart
- Uart1.putc() - послать байт находящийся в регистре 'a'
- Uart1.puts(s) - послать 0-терминированную строку лежащую по адресу s
- Uart1.getc - принять байт, положить его в регистр 'a'
Кроме скорости надо дополнительно инициализировать "ноги", а они для разных контроллеров могут быть разными поэтому это надо сделать отдельной командой:
- Uart.atD5D6
- Uart.atA4A5
Можно конечно это сделать и используя просто gpio...
- Spi.init(csport,cspin,prescaler) - инициализация, выбор линии CS
- Spi.cs() - начало обмена
- Spi.write() - послать байт
- Spi.read() - прочитать байт
- Spi.nocs() - конец обмена
- I2c.init(inputclock,prescaler,bits) - инициализация
- I2c.start() - начало обмена
- I2c.addr(address) - послать номер устройства для обмена
- I2c.write() - послать байт из a
- I2c.stop() - конец обмена
- I2c.read() - принять байт, ждать ACK
- I2c.read.last() - принять последний байт, ACK не ждать, заодно выполняет stop
- I2c.read.array(buffer,length) - принять несколько байт, положить в массив, в конце выполняет stop
- Iwdg.init(prescaler,counter) - запуск и инициализация
- Iwdg.feed - "кормим собаку"
Продолжение следует
Транслятор прилагается в виде .exe файла под windows (net). Есть исходники, и немного откорректировав их вы можете запускать их и в linux из под соответствующего интерпретатора (fsharp)
Подготовка к установке: поскольку мы пользуемся нативным ассемблером st он должен быть у вас на компьютере (stvd с сайта st.com). Транслятор ищет его toolset по пути: C:\Program Files (x86)\STMicroelectronics\st_toolset\asm, ну а для avr придется поставить winavr так чтобы путь до toolset был: C:\Program Files (x86)\WinAVR\avr\bin\
Установка проста: Скопировать каталог naivsdk в корень диска c: и прописать в windows путь до c:\naivesdk\bin запускаемый файл 'pceudo.exe'. Запустите его из коммандной строки... увидите:
Naive pseudo assembler. Copyright 2018 Yury Botov Use: pseudo filename.fileext {-- paramname paramvalue} { --paramname} filename : your pseudo-assembler file fileext : file extension. It must be '.stm8s','.stm8l', '.avr', '.msp430' parameters... --help : this information --sdk sdkpath : path to naivesdk directory - default value 'c:' --inc incpath : addition path to ONE user include directory - default value bsent, if needs more include directories use: --inc first\ --inc second\ --inc third\ .. Target assembler file will be 'filename.asm' Необработанное исключение: System.Exception: Use right parameters ....
Исключение в конце тут норма. Потом может уберу но сейчас оно мне удобно для отладки.
То есть... все просто: запускаем с программу с именем файла псевдоассемблера. Файл псевдо ассемблера должен иметь расширение соответсвующее классу контроллеров '.stm8s','.stm8l', '.avr', '.msp430'. Да, если не хочется хранить sdk в корне диска 'c:' можно его положить и в другое место, но тогда придется прописать полный путь путь до \naivesdk\bin в PATH windows и при запуске указывать --sdk path ...
И еще, по поводу мест где транслятор ищет заголовочные файлы: посмотрите в src/filefinder.fs В двух словах он ищет файл без расширения или с расширением таким же как у файла который ему подсунули. В текущей папке или в папке /inc, потом в соответствующих папках sdk, потом в пользовательских если они были заданы с использованием ключа --inc.
В каталоге naivesdk/stm8s/examples есть примеры и шаблон для создания новых приложений template.stm8s B каталоге naivesdk/stm8s/targets - заголовочные файлы и макросы