Поскольку мигающим светодиодом интерфейс пользователя построить сложно, решил подключить к STM32F407 простенький SPI дисплей из Китая. Интерфейс дисплея PCD8544, подключение по SPI, на aliexpress и ebay обычно ищется по строке LCD nokia 5110, питание дисплея как раз от 2.7 до 3,3 вольт.
По информации полученной из даташита частота SPI дисплея не должна превышать 4 Мгц, однако все имеющиеся у меня экземпляры надежно работали при такте SPI в 21 Мгц…

Описание проекта, исходные тексты программы, настроенный файл автоматической компиляции и сборки проекта под «катом»:

В проект из шестой части публикации добавил работу с счетчиком тактов микроконтроллера DWT (честно взято с www.stm32asm.ru и переформатировано в модуль под раздельную компиляцию). Теперь в каталоге периферии микроконтроллера три модуля:



В корень проекта добавил папку devices — в нее планирую размещать модули подключаемых к микроконтроллеру устройств:



Из устройств самым необходимым сейчас являются FullHD монитор + PC клавиатура + Мышь простой LCD дисплей — чтобы можно было наконец получать информацию не с горящих светодиодов, а читая с экрана. Поэтому в папке devices появилась папка LCD со следующим содержимым:



Файл lcd_func.asm это модуль графических функций которые не зависят от «железа», в настоящий момент содержит в себе следующие функции:

@ ***************************************************************************
@ *               МОДУЛЬ ДОПОЛНИТЕЛЬНЫХ ФУНКЦИЙ МОДУЛЯ LCD                  *
@ ***************************************************************************
@ * Процедуры:								    *
@ *     LCD_PUTSTR:   Вывод строки по координатам:                          *
@ *                   R0:Y, R1:X, R2:COLOR R4:ADR_TEXT                      *
@ *                   Текст должен заканчиваться нулевым символом           *
@ *                   Управляющий код: 0x01, .short Y, .short X, .word col  *
@ *                                                                         *
@ *     LCD_PUTHEX:   Вывод шестнадцатеричного числа:                       *
@ *                   R0:Y, R1:X, R2:COLOR R4:HEXVal, R5:DigitCol           *
@ *                                                                         *
@ *     LCD_PUTDEC:   Вывод десятичного числа:				    *
@ *                   R0:Y, R1:X, R2:COLOR R4:DECVal, R5:DigitCol           *
@ *                                                                         *
@ *     LCD_LINE:     Вывод линии:                                          *
@ *                   R0:Y1, R1:X1, R2:COLOR R3:Y2, R4:X2                   *
@ *                                                                         *
@ *     LCD_RECT:     Вывод прямоугольника:                                 *
@ *                   R0:Y1, R1:X1, R2:COLOR R3:Y2, R4:X2                   *
@ *                                                                         *
@ *  ПРИМЕЧАНИЕ !                                                           *
@ *  - Все процедуры не портят регистры!                                    *
@ *                                                                         *
@ *  - допустимые координаты и шаг печати native процедурами определяются   *
@ *    в файле lcd_param.inc который должен лежать рядом с файлом модуля    *
@ *                                                                         *
@ *  - Драйвер дисплея должен содержать функции определенные как .global:   *
@ *    LCD_CHAR: (R0:Y, R1:X, R2:Color, R3:Char) - native вывод символа     *
@ *    LCD_PIXEL: (R0:Y, R1:X, R2:Color)         - native вывод пиксела     *
@ *                                                                         *
@ ***************************************************************************

Поскольку модуль планируется использовать для различных типов дисплеев, то процедуры старался сразу написать в расчете на разнообразное применение, например, под кодирование цвета отвел 32 бита (регистр R2) — для монохромного дисплея конечно многовато (достаточно было одного бита), но вот для цветных дисплеев с 16-ти битной палитрой в самый раз.

Для рисования линий применил алгоритм Брезенхейма — могу сказать, что ассемблер ARM в этом себя показал просто великолепно! Возможность условного исполнения команд присваивания и арифметических действий сделала код рисования линии не намного большим процедуры вывода символа.

Вывод десятичного числа написан с использованием блока деления на 10 при помощи арифметики и сдвигов — тоже получилось очень компактно.

Файл настроек для модуля lcd_func.asm под драйвер дисплея pcd8544 выглядит пока так:

lcd_param.inc
@GNU AS
@ ***************************************************************************
@ *    Файл настроек для модуля дополнительных функций LCD (lcd_func.asm)   *
@ ***************************************************************************
@ *    Файл  настроек  сообщает  параметры  драйвера  дисплея  для  функци- *
@ * онирования  библиотек дополнительных функций LCD,  это такие параметры, *
@ * как: разрешение экрана по горизонтали и вертикали в графическом режиме, *
@ * наличие native процедуры печати символов,  и возможность  их  печати  с *
@ * точностью до пиксела, шаг вывода текста по X и Y для внешней  процедуры *
@ * печати строк, и т.д.                                                    *
@ *                                                                         *
@ * файл настроек необходимо положить в папку с модулем lcd_func.asm  кото- *
@ * рый их загрузит и будет использовать в своих функциях                   *
@ ***************************************************************************
@
@ Устройство: LCD SPI PCD8544
@

@ графические параметры дисплея
.equ LCD_PARAM_pixel_present, 1 @ в драйвере есть процедура вывода точек LCD_PIXEL

.equ LCD_PARAM_size_px, 84	@ количество точек на экране по горизонтали
.equ LCD_PARAM_size_py, 48      @ количество точек на экране по вертикали


@ текстовые параметры дисплея
.equ LCD_PARAM_char_present, 1    @ в драйвере есть процедура печати букв LCD_CHAR

.equ LCD_PARAM_char_stepx,    7      @  - шаг текста по оси Х 
.equ LCD_PARAM_char_stepy,    8      @  - шаг текста по оси Y


еще не все параметры использую, отложил это до того момента как запущу еще какой нить дисплей (скорее всего это будет цветной SPI дисплей с aliexpress) — вот тогда и буду вводить дополнительные блоки условной компиляции в зависимости от типа дисплея.

В папке PCD8544 размещен драйвер дисплея, это 3 файла:


  • font6x8.inc — шрифт 6х8, немного страшненький, но зато рисовал сам, маленьких букв нет (не получаются они у меня), состоит из знаков, цифр, заглавных букв латинского и русского алфавита. За счет отсутствия маленьких букв — существует в виде двух блоков символов LCD_LAT_CHARS (64 символа) и LDC_RUS_CHARS (31 символ), всего 570 байт
  • lcd_param.inc файл настроек для lcd_func.asm, при использовании драйвера этот файл должен быть скопирован в одну с ним папку (что я и сделал — смотрите содержимое папки LCD приведенное выше)
  • lcd_pcd8544.asm драйвер дисплея. Содержит в себе процедуру инициализации дисплея (LCD_INIT), процедуру очистки буфера (LCD_CLEAR), процедуру обновления экрана содержимым буфера (LCD_REFRESH), процедура рисования пиксела (LCD_PIXEL), процедура вывода символа (LCD_CHAR). В последней процедуре решил немного по «выпендриваться» и сделал вывод символов с точностью до пиксела (причем не через попиксельный вывод каждой точки)

Еще раз повторю: никаких дополнительных функций (вывод числа, вывод строки, вывод линии) в драйвер намеренно не вмещал — это функции от железа не зависящие.

Как всегда самое сложное — это написать демонстрацию возможностей получившихся модулей… Но это я наконец-то сделал:



У меня получилось 3 слайда:
— первый, (он же в начале статьи) — вывод текста с точностью до пиксела, вывод рамки



— второй — вывод шестнадцатеричного числа (до 32ух бит, число цифр задается в программе), вывод десятичного числа (32 бита, число цифр задается в программе)



— третий — рисование линий и точек, внизу слайда приведено количество тактов микроконтроллера затраченное на такое рисование (вместе с очисткой буфера), подсчет тактов производился при помощи модуля DWT:



Да, и еще, чур на дисплей не пинять, он у меня какой-то контуженый. Был другой, но отказался заводиться не только от программы написанной на ассемблере, но и от программы написанной на си с частотой SPI в 2 Мгц (за что сразу был выброшен в мусорку дабы не портил нервы).

Снял небольшое видео работы программы:



Музыка в фоне от телевизора, звуком я пока не занимался.

Ссылка на архив проекта.

P.S. Кстати, никто не занимается написанием текстовых редакторов? Очень хочется среду разработки с подсветкой кода, контекстным меню, и прочими рюшками… Пока делаю это в FAR, но хочется чего то более удобного. Мои контакты прежние: gorbukov @ тот_кто_знает_ всё. ru

Комментарии (7)


  1. beeruser
    29.01.2016 13:48
    +1

    >> Все процедуры не портят регистры!
    Какой в этом смысл?

    Прелесть асма как раз в том, что не нужно соблюдать стандартный ABI.

    Весь код не смотрел, но глянул LCD_PIXEL
    Скажу что у вас там не асм, а скорее C сконвертированный в асм.
    Более того, если бы это был чистый C, функция скорее всего просто была бы заинлайнена в вызывающий код и работала бы в 2 раза быстрее.

    Вот это вообще неудачный код

    MOV R3, 1
    AND R0, R0, 0x07
    LSL R3, R3, R0
    CMP R2, 0x01
    ITEE EQ
    ORREQ R4, R4, R3
    RSBNE R3, R3, 0xFF
    ANDNE R4, R4, R3

    RSB AND (&~) заменяются одной командой BIC.
    Вся эта последовательность не более чем вставка битового поля, выполняемая инструкцией BFI

    Можно сдвинуть R2 и потом
    AND R4, R3
    OR R4, R2

    У M4 есть инструкции типа MLA, ROR, BFC, ORN, которых также нет в C

    Можно, например написать так (не проверено):

    r0 y
    r1 x
    r2 0/1

    lcd_pixel:
    push {r0,r1,r3,r4}
    lsr r3, r0, #3
    mov r4, #48
    mla r3, r4, r3, r1
    movw r1, #(48/8*84)
    cmp r3, r1
    blt 1f
    ldr r1, =lcd_buff
    add r3,r1
    ldrb r1, [r3]
    ror r1, r1, r0
    rsb r0, r0, #0
    bfi r1, r2, #0, #1
    ror r1, r1, r0
    strb r1, [r3]
    1: pop {r0, r1,r3,r4}
    bx lr

    -5 инструкций
    -1 бранч
    -3 сохранённых в стеке регистра

    Я под thumb2 праада не писал и не проверял, так что скорее всего не скомпиляется =)
    Но тем не менее, посыл должен быть ясен — раз вы занялись асм-ом, используете возможности, которые предоставляет процессор.
    Также не нужно сохранять то, что не меняется.
    Даже если использовать сишный ABI, push/pop можно вообще убрать, а вместо r4 использовать r12

    >> кстати, никто не занимается написанием текстовых редакторов? очень хочется среду разработки с…

    А чем все существующие не подходят, интересно?


    1. beeruser
      29.01.2016 19:00

      Опс. Думал про BIC, а написал AND
      BIC R4, R3
      ORR R4, R2
      =)


      1. VitGo
        01.02.2016 10:58

        Такую проверку координат не хочу:

        lsr r3, r0, #3
        mov r4, #48
        mla r3, r4, r3, r1
        movw r1, #(48/8*84)
        cmp r3, r1
        blt 1f
        

        это не проверка координат а скорее проверка выхода за границы буфера экрана… да и при попытке задать допустимый параметр Y но не допустимый по X — потом замучаешься отлаживаться

        а вот циклический сдвиг — это красивое решение!!!
        ldrb r1, [r3]
        ror r1, r1, r0
        rsb r0, r0, #0
        bfi r1, r2, #0, #1
        ror r1, r1, r0
        strb r1, [r3]
        


        постараюсь попробовать сегодня вечером!


    1. VitGo
      01.02.2016 17:12

      в общем реализовал ваш алгоритм со сдвигом

      правда с небольшими уточнениями

      .global LCD_PIXEL
      LCD_PIXEL:                       
      		CMP	R0, 48		@ проверим допустимость координат
      		BPL	LCD_PIXEL_exit
      		CMP	R1, 84
      		BPL	LCD_PIXEL_exit
      
                      PUSH	{R1, R3, R4}
      
      		@ вычисляем адрес пиксела
      		LDR	R3, =LCD_BUFF
      		ADD	R3, R3, R1       @ ADR + x
      
      		LSR	R1, R0, 3  	 @  y >> 3
      		MOV	R4, 84           @
      		MLA	R3, R1, R4, R3   @ R3=(y>>3)*84+ADR+x 
      
      		LDRB	R1, [R3]         @ читаем байт бита
      
                      @ новый вариант наложения маски символа (1 - ставим, 0 - стираем)		
      		AND	R4, R0, 0x07
      		ROR	R1, R1, R4
      		RSB     R4, R4, 32
              	BFI	R1, R2, 0, 1
      		ROR	R1, R1, R4		
      		STRB	R1, [R3]	@ запись в буфер
      
      	        POP	{R1, R3, R4}
      
      LCD_PIXEL_exit:
      		BX	LR
      


      прирост скорости 80%!!! (38225 тактов на рисование третьего слайда)


  1. VitGo
    29.01.2016 14:09

    По поводу BIC / BFI — посмотрю, просто сам ассемблер только изучаю, и ресурсов в которых бы понятно объяснялась логика команд в сети от совсем мало до совсем нет (хотелось бы видеть примеры вида: на входе / на выходе, а везде только названия из которых не всегда понятно что они собой представляют) — на счет того что написано по Си-шному — в точку! со своего старого исходника на Си и делал! :-)

    Теперь когда у меня появился вывод на LCD — у меня как раз запланировано изучение команд, я наметил себе следующую часть публикации именно по командам, чтобы полностью разобрать какая и как работает…

    На счет сохранения регистров — ну по процедурам _INIT согласен, на счет других же — абсолютно не согласен! процедуры тогда и только тогда хороши — когда не нужно думать о том что будет после них… в том же драйвере дисплея есть подпрограммы проверки флагов, управления линиями управления дисплея — вот они регистры не сохраняют, и это уже моя забота была за этим следить… а все что отдается на .global — должно гарантировано не портить регистры (ну по крайней мере где это логично)

    А какие из существующих подходят для ассемблера (я не про подсветку кода говорю это как раз низкоприоритетное желание)?
    у меня вот получился следующий список желания (я еще не сортировал по очередности и нужности):
    1) Подсветка кода ассемблера
    2) Авто дополнение вводимого кода (PUSH -> POP, IT — IT Block, и т.д.)
    3) Контроль глобальных и локальных меток
    4) Подсказка по инструкциям ассемблера
    5) Контроль параметров подпрограмм
    6) Поддержка программ как модулей с возможностью их добавления и удаления
    7) Контроль в редакторе опций условного исполнения
    8) Перенаправление сообщений консоли на себя, компиляция и компановка при помощи GNU AS
    9) Файлменеджер проекта с крупными значками
    10) Закладки в файл менеджере (переход в нужную папку одним кликом)
    11) Переход на метку программы в редакторе
    12) Автоматическое формирование списка констант модуля и дописывание в начало
    13) Система помощи по регистрам настройки микроконтроллера
    14) Мастеркода (автосоставитель кода) — например для настройки GPIO, SPI, DCMI и т.д.
    15) Эмуляция исполнения кода
    16) Настраиваемые окна среды (все! а не только окно редактора)
    17) Отладка (пока не понимаю как, но как задача висит)


    1. beeruser
      29.01.2016 19:25

      >> на счет других же — абсолютно не согласен! процедуры тогда и только тогда хороши — когда не нужно думать о том что будет после них
      Ну как хотите. Используйте стандартный ABI хотя бы. По крайней мере можно будет работать с другими языками.
      http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.subset.swdev.abi/index.html

      >> у меня вот получился следующий список желания
      Ну этого вы до скончания веков будете ждать =)


      1. VitGo
        29.01.2016 19:50

        на счет стандартного ABI — спасибо за подсказку, посмотрел, подумаю…
        просто на асме я давно программирую (это на ARM недавно стал смотреть по серьездному), и у меня всегда низшие регистры являются расходными…
        а вот сколько их будет — я себя не ограничиваю…

        на счет списка желаний — если бы нашелся программист на Delpi (я как то очень давно писал на нем немного) и если бы мне хотябы каркас дали — то я бы дописывал потихоньку…
        самому конечно разбираться не очень хочется с нуля, потому что тут что то одно — либо в асме разбираешься и максимально погружаешься либо во что то еще…

        в принципе список за исключением некоторых пунктов не такой уж и сложный, по крайней мере у меня в голове задача структурируется, и я потихоньку функциональность напишу… но вот что касается интерфейса программы — то тут я точно не смогу :-((( (ну по меньшей мере пока не начну погружаться по серьездному)