Skip to content

Latest commit

 

History

History
335 lines (260 loc) · 22.8 KB

README.md

File metadata and controls

335 lines (260 loc) · 22.8 KB

Pceudo

Захотелось мне более читаемого и интуитивного ассемблера. Сейчас более менее работает версия под 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 на одной строке а его метка на другой). Необходимо конечно помнить и об ограничениях в режимах адресации команд нативного ассемблера - они само собой присутствуют и транслятор никак от этого не спасает.

Подробности установки в конце файла этого файла.

Операции

  1. КОМЕНТАРИЙ - // текст, можно ставить как в начале так и после любой команды кроме 'define' препроцессора
  2. ПРИСВАИВАНИЕ - X = Y ( под присваиванием подразумевается и обнуление, и установка/сброс флага и т.п.)
  3. СЛОЖЕНИЕ - X += Y
  4. ВЫЧИТАНИЕ - X -= Y
  5. УМНОЖЕНИЕ - *X = Y ( в версии stm8 реально доступны только x *= a или y *= a)
  6. ДЕЛЕНИЕ - X /= Y ( в версии stm8 реально доступны только x/=a, y/=a или x/=y - последний меняет и x и y - в y возвращает остаток от деления )
  7. ИНВЕРСИЯ ЗНАКА - -X
  8. ПРОВЕРКА ФЛАГОВ - ?X
  9. СРАВНЕНИЕ X ? Y ( в версии stm8 реально доступны только a?... x?... y?...)
  10. ИНКРЕМЕНТ - X++ или ++X (два варианта полностью эквивалентны)
  11. ДЕКРЕМЕНТ - X-- или --X (два варианта полностью эквивалентны)
  12. ПОБИТОВОЕ AND - X &= Y
  13. ПОБИТОВОЕ OR - X |= Y
  14. ПОБИТОВОЕ XOR - X ^= Y
  15. ПОБИТОВОЕ NOT - !X
  16. ПОБИТОВОЕ СРАВНЕНИЕ - X &? Y ( в версии stm8 реально доступны только a&?)
  17. КОЛЬЦЕВОЙ СДВИГ ЧЕРЕЗ С - c<X<c
  18. КОЛЬЦЕВОЙ СДВИГ ЧЕРЕЗ С - c>X>c
  19. СДВИГ УМНОЖЕНИЕ НА 2 - c<X<0
  20. СДВИГ ЗНАКОВОЕ ДЕЛЕНИЕ НА 2 - s>X>c
  21. СДВИГ БЕЗЗНАКОВОЕ ДЕЛЕНИЕ НА 2 - 0>X>c
  22. КОЛЬЦЕВОЙ СДВИГ ЧЕРЕЗ A - a<X<a ( X только x или y)
  23. КОЛЬЦЕВОЙ СДВИГ ЧЕРЕЗ A - a>X>a ( X только x или y)
  24. ВЫЗОВ ФУНКЦИИ CALL - call near X или call X или call far X
  25. ВОЗВРАТ ИЗ ФУНКЦИИ - ret или ret far
  26. ВОЗВРАТ ИЗ ПРЕРЫВАНИЯ - iret
  27. ПЕРЕХОД БЕЗУСЛОВНЫЙ - go near X или go X или go far X
  28. ПЕРЕХОД УСЛОВНЫЙ - if condition X
  29. ПРОПУСК УСЛОВНЫЙ - skip X#Y=Z (в avr)
  30. ОСТАНОВ - halt
  31. ОСТАНОВ С ОЖИДАНИЕМ СОБЫТИЯ - wait
  32. ОСТАНОВ С ОЖИДАНИЕМ ПРЕРЫВАНИЯ - wait i
  33. ПРОГРАММНОЕ ПРЕРЫВАНИЕ - trap
  34. НИЧЕГО НЕ ДЕЛАНИЕ nop или break
  35. ЖДАТЬ ПОКА БИТ НЕ СТАНЕТ ?->

Комбинированные операции - генерируют несколько команд:

  1. ПЕРЕХОД УСЛОВНЫЙ - if чтото=0 X или if чтото<>0 X где что то - регистр или переменная

Вставка нативных операций процессора:

  1. ВСТАВКА НАТИВНОЙ КОМАНДЫ inline cmd X,Y; (внутри inline строки только одна строка ассемблера, но в ней можно использовать препроцессор нативного ассемблера это дает дополнительную свободу при составлении своих макросов)

Режимы адресации

  1. НЕПОСРЕДСТВЕННЫЙ - 123 - число или там где уместно 'c' - символ
  2. РЕГИСТРОВЫЙ - r - имя регистра. Может быть a,cc,x,xl,xh,y,yl,yh,sp результат - значение регистра
  3. ОТНОСИТЕЛЬНЫЙ - label - относительный адрес (метка при адресации)
  4. ПРЯМОЙ - label - результат - значение переменной по адресу данной метки
  5. ПОРТ ВВ - - результат - значение порта (это для avr)
  6. ИНДЕКСНЫЙ - [r] - имя регистра может быть x,y,sp результат - значение переменной по адресу из регистра
  7. ИНДЕКСНЫЙ - label[r] - имя регистра может быть x,y,sp результат - значение переменной по адресу label со смещением из r
  8. НЕПРЯМОЙ - [label] - значение переменной из расположенной по адресу из ячейки памяти расположенной по адресу хранящейся в ячейке памяти
  9. НЕПРЯМОЙ - [label[r]] - имя регистра может быть x,y,sp результат - значение переменной из расположенной по адресу из ячейки памяти расположенной по адресу label со смещением из r
  10. ПОБИТОВЫЙ - label#bit - результат - значение бита по адресу данной метки с номером bit
  11. ПОБИТОВЫЙ К ПОРТУ ВВ - #bit - результат - значение бита по адресу данной метки с номером bit (для avr)

Метки

  1. МЕТКА С АДРЕСОМ - label@address:
  2. МЕТКА - label:

Данные

  1. ПРИВЯЗАННЫЕ ПЕРЕМЕННЫЕ - (u8), (u16), (u32) - нужны для привязывания имени к какому либо месту, используются например для описания регистров ввода вывода
  2. НЕИНИЦИАЛИЗИРОВАННЫЕ ПЕРЕМЕННЫЕ - (x8), (x16), (x32) где x = {r,m} r - псевдорегистровая память, m - оперативная память
  3. ИНИЦИАЛИЗИРОВАННЫЕ ЗНАЧЕНИЯ - (x8) {val}, (x16) {val}, (x32) {val} где x = {f,e} f - флэш память, e - eeprom
  4. ПРИВЯЗАННЫЕ МАССИВЫ - (u8)[n], (u16)[n], (u32)[n] где n - размерность массива
  5. НЕИНИЦИАЛИЗИРОВАННЫЕ МАССИВЫ - (x8)[n], (x16)[n], (x32)[n] где x = {r,m} r - псевдорегистровая память, m - оперативная память, n - размерность массива
  6. ИНИЦИАЛИЗИРОВАННЫЕ МАССИВЫ - (x8)[n] {vals}, (x16)[n]{vals}, (x32)[n]{vals}, (x8)[n]"line" где x = {f,e} f - флэш память, e - eeprom, vals,line - данные для инициализации массива

Препроцессор

  1. ПОДКЛЮЧИТЬ ФАЙЛ - include filename{.ext}
  2. ОПРЕДЕЛИТЬ КОНСТАНТУ - define const{ value}
  3. ОПРЕДЕЛИТЬ МАКРОС - define name(parameter1{,parameter2...}) macros_body

В макросе можно использовать несколько строк:

define a(b) a=b;\ // обязательно завершайте команды в обрезанных строках символом ';'

b++;\

c=a;

В макросе можно использовать локальные метки:

define a(b) @1: b--; if z=0 @1;

  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)

Сначала чуть чуть о stm8 после reset:

  • тактировние от внутреннего генератора (hsi) с множителем 8 - частота fmaster = 2 МГц
  • тактирование всех периферийных устройств включено
  • прерывания глобально запрещены

Итак...

Системные, файл 'system'

  • Stack.init(param) - инициализирует указатель стека в значение 'param'
  • Interrupt.disable - общий запрет прерываний
  • Interrupt.enable - общее разрешение прерываний

Прерывния (я обернул столь простые команды - i=0 и i=1 в макрос потому что интуитивно не понятно что 1 это запрет, а 0 разрешение)

Порты ввода-вывода, файл 'gpio'

Дам имена макросов для 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'

  • Power.on.all - включить тактирование всех устройств
  • Power.off.all - выключить тактирование всех устройств
  • Power.on.имяустройства - включить тактирование устройства с указанным именем
  • Power.off.имяустройства - выключить тактирование устройства с указанным именем

Допустимые имена устройств: tim1,tim2,tim3,tim4,uart1,uart2,spi,i2c,awu,adc

Запись в EEPROM и FLASH, файл 'flash'

  • Flash.unprotect - разрешить запись в область flash
  • Flash.protect - запретить запись во flash
  • Eeprom.unprotect - разрешить запись в область eeprom
  • Eeprom.protect - запретить запись во eeprom

Последовательные порты простой обмен без буферизации, файлы 'uart1', 'uart2', 'uart3'

Оба 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 простой обмен, файл 'spi'

  • Spi.init(csport,cspin,prescaler) - инициализация, выбор линии CS
  • Spi.cs() - начало обмена
  • Spi.write() - послать байт
  • Spi.read() - прочитать байт
  • Spi.nocs() - конец обмена

I2C просой обмен, файл 'i2c'

  • 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 независимый watchdog, файл 'iwdg'

  • 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 - заголовочные файлы и макросы