@.CHARSET CP1251
@GNU AS
@.DESC     type=module
@ ***************************************************************************
@ *                  МОДУЛЬ УПРАВЛЕНИЯ LCD НА ST7789                        *
@ *   25.08.2022,  Автор модуля: Александр Епифанцев, Telegram: @alexdx_t   *
@ ***************************************************************************
@ * Процедуры:                                                              *
@ *     LCD_INIT:     Инициализация SPI, LCD (инициализации GPIO нет !)     *
@ *     LCD_FILLREC   Заполнение области цветом                             *
@ *     LCD_CHAR:     Вывод символа                                         *
@ *                                                                         *
@ *  ПРИМЕЧАНИЕ !                                                           *
@ *  - допустимые координаты: 0<X<239, 0<Y<239                              *
@ *  - вывод символов LCD_CHAR с точностью до пискела                       *
@ *  - в знакогенераторе нет прописных (маленьких) букв                     *
@ *                                                                         *
@ ***************************************************************************
@.ENDDESC

.SYNTAX unified                     @ синтаксис исходного кода
.THUMB                              @ тип используемых инструкций Thumb
.CPU    cortex-m4                   @ процессор
.FPU    fpv4-sp-d16                 @ сопроцессор

.INCLUDE  "/src/devices/ST7789/lcd_st7789.inc"
.INCLUDE  "/src/inc/rcc.inc"
.INCLUDE  "/src/inc/gpio.inc"
.INCLUDE  "/src/inc/spi.inc"

@ подключение дисплея

@ .EQU  pCS                           , 11
@ .EQU  pCS_GPIO_BASE                 , GPIOB_BASE

.EQU  pBL                           , 3
.EQU  pBL_GPIO_BASE                 , GPIOB_BASE

.EQU  pDC                           , 12
.EQU  pDC_GPIO_BASE                 , GPIOB_BASE

.EQU  pRST                          , 14
.EQU  pRst_GPIO_BASE                , GPIOB_BASE

@ аппаратный SPI (смотрите файл настройки GPIO для выбранного SPI)
.EQU  LCD_SPI                       , SPI2_BASE

.SECTION .asmcode
@.DESC     name=LCD_INIT type=proc
@ +--------------------------------------------------------------------+
@ |               Процедура инициализации дисплея ST7789               |
@ +--------------------------------------------------------------------+
@ | Процедура проводит инициализацию: SPI, и самого дисплея            |
@ | Инициализацию GPIO нужно выполнить до вызова этой процедуры        |
@ +--------------------------------------------------------------------+
@.ENDDESC

.GLOBAL LCD_INIT
LCD_INIT:                LKU:
                         PUSH        { R0, R1, LR }
                    @ включим SPI 2
                         MOV         R0, 1
                         LDR         R1, = ( PERIPH_BB_BASE + ( RCC_BASE + RCC_APB1ENR ) * 32 + RCC_APB1ENR_SPI2EN_N * 4 )
                         STR         R0, [ R1 ]

          @ настройка и включение интерфейса SPI
@ .EQU  spi_dir            , SPI_CR1_BIDIOE | SPI_CR1_BIDIMODE    @ SPI_Direction_1Line_Tx
.EQU  spi_dir            , SPI_CR1_BIDIOE    @ SPI_Direction_1Line_Tx
.EQU  spi_mode_master    , SPI_CR1_MSTR | SPI_CR1_SPE        @ SPI_Mode_Master & SPI_Enable
.EQU  spi_nss            , SPI_CR1_SSM | SPI_CR1_SSI         @ SPI_NSS_Soft
.EQU  spi_brpresc        , SPI_CR1_BR_DIV_2                  @ делитель частоты для SPI

               @ включаем SPI2 с выбранными выше настройками
                         LDR         R1, = ( PERIPH_BASE + LCD_SPI )
                         LDR         R0, = ( spi_dir | spi_mode_master | spi_nss | spi_brpresc | SPI_CR1_CPOL)
                         STR         R0, [ R1, SPI_CR1 ]

          @ начало работы с дисплеем

               @ укажем дисплею что работаем с ним
                       @  BL          LCD_CS0

               @ аппаратный сброс LCD
                         @ установим линию RST=0
                         MOV         R0, 0
                         LDR         R1, = ( PERIPH_BB_BASE + ( pRst_GPIO_BASE + GPIO_ODR ) * 32 + pRST * 4 )
                         STR         R0, [ R1 ]
                         MOV         R0, 10                 @ пауза для фиксации сигнала сброса устройством
                         BL          SYSTICK_DELAY
                         MOV         R0, 1
                         STR         R0, [ R1 ]             @ установим линию RST=1
                         MOV         R0, 100                @ пауза после сброса
                         BL          SYSTICK_DELAY

               @ отправка команд инициализации LCD
                         MOV         R5, ST7789_Cmd_SWRESET @ SoftReset
                         BL          LCD_SENDCOM
                         MOV         R0, 130                @ пауза после SoftReset
                         BL          SYSTICK_DELAY

                         MOV         R5, ST7789_Cmd_SLPOUT  @ разбудим дисплей после сброса
                         BL          LCD_SENDCOM
                         MOV         R0, 100                @ подождем пока проснется
                         BL          SYSTICK_DELAY

               @ Установка режима цвета
                         MOV         R5, ST7789_Cmd_COLMOD  @ режим цвета
                         BL          LCD_SENDCOM
                         MOV         R5, (ST7789_ColorMode_65K | ST7789_ColorMode_16bit) & 0x77               @ 16ти битный цвет
                         BL          LCD_SENDDATA
                         MOV         R0, 10                @
                         BL          SYSTICK_DELAY

@ ST7789_MemAccessModeSet(4, 1, 1, 0);
@ uint8_t Rotation = 4, uint8_t VertMirror = 1, uint8_t HorizMirror = 1, uint8_t IsBGR = 0
@                         MOV         R5, ST7789_Cmd_MADCTL     @ направление вывода изображения и формат rgb
@                         BL          LCD_SENDCOM
@                         MOV         R0, ST7789_MADCTL_MV | ST7789_MADCTL_ML | ST7789_MADCTL_MH
@                         MOV         R5, ST7789_MADCTL_MH
@                         BL          LCD_SENDDATA
                         MOV         R0, 10                @
                         BL          SYSTICK_DELAY

                         MOV         R5, ST7789_Cmd_INVON     @ режим инверсии
                         BL          LCD_SENDCOM
                         MOV         R0, 10                @
                         BL          SYSTICK_DELAY

@ ST7789_FillRect(0, 0,  ST7789_Width, ST7789_Height, color);

@ void ST7789_SetBL(uint8_t Value) @ включить подсветку
                         BL          LCD_BL1

                         MOV         R5, ST7789_Cmd_DISPON @ включить отображение
                         BL          LCD_SENDCOM
                         MOV         R0, 10                @
                         BL          SYSTICK_DELAY

                        @ BL          LCD_CS1

          @ отключимся от дисплея, он уже проинициализирован
                         POP         { R0, R1, PC }


@.DESC     name=LCD_SENDCOM type=proc
@ ***************************************************************************
@ *                  Отправка на дисплей байта команды                      *
@ ***************************************************************************
@ | Параметры:                                                              |
@ |  R5 - байт для отправки                                                 |
@ +-------------------------------------------------------------------------+
@.ENDDESC
LCD_SENDCOM:  @ отправка команды с предварительным  ожиданием BSY
                         PUSH        { R0, R1, R2, R3, LR }

                    @ ожидание ухода предыдущих данных или команды
                         BL          SPI_WAIT_TXE
                         BL          SPI_WAIT_BSY            @ ожидание окончания прошлой отправки

                    @ переходим в режим команд  DC=0
                         MOV         R2, 0
                         LDR         R3, = ( PERIPH_BB_BASE + ( pDC_GPIO_BASE + GPIO_ODR ) * 32 + pDC * 4 )
                         STR         R2, [ R3 ]

                         STR         R5, [ R1, SPI_DR ]     @ отправка команды
                    @ ожидание ухода команды
                         BL          SPI_WAIT_TXE           @ ждем когда данные уйдут на передачу
                         BL          SPI_WAIT_BSY           @ ждем физического окончания передачи

                    @ переходим в режим данных DC=1
                         MOV         R2, 1
                         STR         R2, [ R3 ]

                         POP         { R0, R1, R2, R3, PC }

@.DESC     name=LCD_SENDDATA type=proc
@ ***************************************************************************
@ *                  Отправка на дисплей байта данных                       *
@ ***************************************************************************
@ | Параметры:                                                              |
@ |  R5 - байт для отправки                                                 |
@ +-------------------------------------------------------------------------+
@.ENDDESC
LCD_SENDDATA:            @ отправка данных с предварительным ожиданием TXE
                         PUSH        { R1, R2, LR }
                         BL          SPI_WAIT_TXE           @ проверим что прошлый байт ушел на отправку
                         STR         R5, [ R1, SPI_DR ]
                         POP         { R1, R2, PC }


SPI_WAIT_BSY:            @ ожидание конца физической передачи
                         LDR         R2, [ R1, SPI_SR ]
                         TST         R2, SPI_SR_BSY
                         BNE         SPI_WAIT_BSY
                         BX          LR

SPI_WAIT_TXE:            @ ожидание записи данных из spi_dr в shift
                         LDR         R1, = ( PERIPH_BASE + LCD_SPI )
spi_TXE_wait:
                         LDR         R2, [ R1, SPI_SR ]
                         TST         R2, SPI_SR_TXE
                         BEQ         spi_TXE_wait
                         BX          LR


@ управление линией BL - - - - - - - - - - - - - - - - - - - - - - -
LCD_BL0:
                         PUSH        { R0, R1 }
                         MOV         R0, 0
                         LDR         R1, = ( PERIPH_BB_BASE + ( pBL_GPIO_BASE + GPIO_ODR ) * 32 + pBL * 4 )
                         STR         R0, [ R1 ]
                         POP         { R0, R1 }
                         BX          LR

LCD_BL1:
                         PUSH        { R0, R1 }
                         MOV         R0, 1
                         LDR         R1, = ( PERIPH_BB_BASE + ( pBL_GPIO_BASE + GPIO_ODR ) * 32 + pBL * 4 )
                         STR         R0, [ R1 ]
                         POP         { R0, R1 }
                         BX          LR



@.DESC     name=LCD_AT type=proc
@ ***************************************************************************
@ *                  Задание прямоугольной области экрана                   *
@ ***************************************************************************
@ | Задание области экрана для операций                                     |
@ | Параметры:                                                              |
@ |  R1 - координаты X: 31:16 - SX, 15:0 - EX                               |
@ |  R0 - координаты Y: 31:16 - SY, 15:0 - EY                               |
@ +-------------------------------------------------------------------------+
@.ENDDESC
LCD_AT:                  @ определение прямоугольной области экрана
                         PUSH        { R0, R1, R5, LR }

                         MOV         R5, ST7789_Cmd_CASET   @ задать координаты StartX, EndX     столбец
                         BL          LCD_SENDCOM            @ отправка команды
                         @ UBFX R0, R1, #lsb, #width     Копирует поле R1 в мл. биты R0 с заполнением нулями
                         UBFX        R5, R1, 24, 8          @ отправка StartX
                         BL          LCD_SENDDATA
                         UBFX        R5, R1, 16, 8          @ отправка StartX
                         BL          LCD_SENDDATA
                         UBFX        R5, R1, 8, 8           @ отправка EndX
                         BL          LCD_SENDDATA
                         UBFX        R5, R1, 0, 8           @ отправка EndX
                         BL          LCD_SENDDATA

                         MOV         R5, ST7789_Cmd_RASET   @ задать координаты StartY, EndY      строка
                         BL          LCD_SENDCOM            @ отправка команды

                         UBFX        R5, R0, 24, 8          @ отправка StartY
                         BL          LCD_SENDDATA
                         UBFX        R5, R0, 16, 8          @ отправка StartY
                         BL          LCD_SENDDATA
                         UBFX        R5, R0, 8, 8           @ отправка EndY
                         BL          LCD_SENDDATA
                         UBFX        R5, R0, 0, 8           @ отправка EndY
                         BL          LCD_SENDDATA

                         MOV         R5, ST7789_Cmd_RAMWR   @ команда заполнения области
                         BL          LCD_SENDCOM

                         POP         { R0, R1, R5, PC }

@.DESC     name=LCD_FILLRECT type=proc
@ ***************************************************************************
@ *                  Заливка прямоугольной области экрана                   *
@ ***************************************************************************
@ | Очистка области экрана и заливка цветом                                 |
@ | Параметры:                                                              |
@ |  R2 - цвет заливки                                                      |
@ |  R0 - координаты Y: 31:16 - START_Y, 15:0 - END_Y                       |
@ |  R1 - координаты X: 31:16 - START_X, 15:0 - END_X                       |
@ +-------------------------------------------------------------------------+
@.ENDDESC
.GLOBAL LCD_FILLRECT
LCD_FILLRECT:
                         PUSH        { R0, R4, R5, R6, R7, LR }

                         BL          LCD_AT        @ установим рабочую область на весь экран

                         @ Расчитаем размер по Y
                         UBFX        R5, R0, 0, 16
                         UBFX        R6, R0, 16, 16
                         SUB         R5, R5, R6
                         @ Расчитаем размер по X
                         UBFX        R7, R1, 0, 16
                         UBFX        R6, R1, 16, 16
                         SUB         R6, R7, R6
                         ADD         R5, R5, 1
                         ADD         R6, R6, 1
                         MUL         R4, R5, R6    @ Общее количество точек заполнения

LCD_CLEAR_loop:
                         UBFX        R5, R2, 8, 8
                         BL          LCD_SENDDATA
                         UBFX        R5, R2, 0, 8
                         BL          LCD_SENDDATA

                         SUBS        R4, R4, 1
                         BNE         LCD_CLEAR_loop

                         BL          SPI_WAIT_TXE
                         BL          SPI_WAIT_BSY

                        @ BL          LCD_CS1                 @ завершим сеанс работы с дисплеем

                         POP         { R0, R4, R5, R6, R7, PC }


@.DESC     name=LCD7789_PIXEL type=proc
@ +-------------------------------------------------------------------------+
@ |              Рисование пиксела с заданными координатами                 |
@ +-------------------------------------------------------------------------+
@ |                                                                         |
@ | Процедура рисует пиксел с координатами                                  |
@ | входные параметры:                                                      |
@ |    R0 - координата Y [0..239],                                          |
@ |    R1 - координата X [0..239],                                          |
@ |    R2 - цвет рисования                                                  |
@ | выходные параметры: нет                                                 |
@ } изменяемые регистры по выходу: нет                                      |
@ +-------------------------------------------------------------------------+
@.ENDDESC
.GLOBAL LCD7789_PIXEL
LCD7789_PIXEL:
                         PUSH        {R0, R1, R2, R5, LR}

                     @ Меняем X  Y  местами
                         MOV         R5, R0
                         MOV         R0, R1
                         MOV         R1, R5

                     @ Создадим область вывода
                         @ Упаковываем координату Y
                         PKHBT       R0, R0, R0, LSL 16
                         ADD         R0, R0, 1
                         @ Упаковываем координату X
                         PKHBT       R1, R1, R1, LSL 16
                         ADD         R1, R1, 1
                         BL          LCD_AT

                     @ передаем цвет
                         UBFX        R5, R2, 8, 8
                         BL          LCD_SENDDATA
                         UBFX        R5, R2, 0, 8
                         BL          LCD_SENDDATA
                         POP         {R0, R1, R2, R5, PC}


@.DESC     name=LCD_CHAR_6x8 type=proc
@ ***************************************************************************
@ *                       Вывод символа по координатам                      *
@ ***************************************************************************
@ | Очистка области экрана и заливка цветом                                 |
@ | Параметры:                                                              |
@ |    LCD_CHAR: (R0:Y, R1:X, R2:Color, R3:Char) - native вывод символа     |
@ +-------------------------------------------------------------------------+
@.ENDDESC
.GLOBAL LCD_CHAR_6x8
LCD_CHAR_6x8:
                         PUSH        { R0, R1, R2, R4, R5, R6, R7, R8, LR }
                     @ рассчитаем адрес символа
                         @ в зависимости от кода символа возьмем нужнуя часть знакогенератора
                         CMP         R3, 192
                         ITTEE       CC
                         LDRCC       R5, = LCD_LAT_CHARS
                         SUBCC       R3, R3, 32
                         LDRCS       R5, = LCD_RUS_CHARS
                         SUBCS       R3, R3, 192

                         MOV         R6, 6
                         MUL         R3, R3, R6
                         ADD         R3, R5, R3             @ в R3 адрес символа

                     @ Меняем X  Y  местами
                         MOV         R6, R0
                         MOV         R0, R1
                         MOV         R1, R6

                     @ Создадим область вывода
                         @ Упаковываем координату Y
                         PKHBT       R0, R0, R0, LSL 16
                         ADD         R0, R0, 5
                         @ Упаковываем координату X
                         PKHBT       R1, R1, R1, LSL 16
                         ADD         R1, R1, 7
                         BL          LCD_AT

                    @ R1 - байт символа
                    @ R2 - color
                    @ R3 - адрес байта символа для вывода
                    @ R6 - счетчик строк символа для вывода
                    @ R7 - счетчик бит
                         MOV         R6, 6
LCD_line_loop:
                      @ передача одной строки символа
                         MOV         R7, 8
                         LDRB        R1, [ R3 ], 1
LCD_pix_loop:
                     @ выбираем цвет точки
                         TST         R1, 0x80
                         ITE         EQ
                         UBFXEQ      R8, R2, 0, 16
                         UBFXNE      R8, R2, 16, 16
                     @ передаем цвет
                         UBFX        R5, R8, 8, 8
                         BL          LCD_SENDDATA
                         UBFX        R5, R8, 0, 8
                         BL          LCD_SENDDATA
                     @ переходим к следующему биту
                         LSL         R1, R1, 1
                     @ цикл передачи бит (снизу вверх)
                         SUBS        R7, R7, 1
                         BNE         LCD_pix_loop
                     @ цикл передачи линий (слева на право)
                         SUBS        R6, R6, 1
                         BNE         LCD_line_loop

                         BL          SPI_WAIT_TXE
                         bl          SPI_WAIT_BSY
                         POP         { R0, R1, R2, R4, R5, R6, R7, R8, PC }


@.DESC     name=LCD_CHAR_12x16 type=proc
@ ***************************************************************************
@ *                       Вывод символа по координатам                      *
@ ***************************************************************************
@ | Очистка области экрана и заливка цветом                                 |
@ | Параметры:                                                              |
@ |    LCD_CHAR: (R0:Y, R1:X, R2:Color, R3:Char) - native вывод символа     |
@ +-------------------------------------------------------------------------+
@.ENDDESC
.GLOBAL LCD_CHAR_12x16
LCD_CHAR_12x16:
                         PUSH        { R0, R1, R2, R4, R5, R6, R7, R8, LR }
                     @ рассчитаем адрес символа
                         LDR       R5, = LCD_CHARS_12x16
                         SUB       R3, R3, 32

                         MOV         R6, 24
                         MUL         R3, R3, R6
                         ADD         R3, R5, R3             @ в R3 адрес символа

                     @ Меняем X  Y  местами
                         MOV         R6, R0
                         MOV         R0, R1
                         MOV         R1, R6

                     @ Создадим область вывода
                         @ Упаковываем координату Y
                         PKHBT       R0, R0, R0, LSL 16
                         ADD         R0, R0, 11
                         @ Упаковываем координату X
                         PKHBT       R1, R1, R1, LSL 16
                         ADD         R1, R1, 15
                         BL          LCD_AT

                    @ R1 - 2 байта символа
                    @ R2 - color
                    @ R3 - адрес байта символа для вывода
                    @ R6 - счетчик строк символа для вывода
                    @ R7 - счетчик бит
                         MOV         R6, 12
LCD_line_loop_12x16:
                      @ передача одной строки символа
                         MOV         R7, 16
                         LDRSH       R1, [ R3 ], 2
LCD_pix_loop_12x16:
                     @ выбираем цвет точки
                         TST         R1, 0x8000
                         ITE         EQ
                         UBFXEQ      R8, R2, 0, 16
                         UBFXNE      R8, R2, 16, 16
                     @ передаем цвет
                         UBFX        R5, R8, 8, 8
                         BL          LCD_SENDDATA
                         UBFX        R5, R8, 0, 8
                         BL          LCD_SENDDATA
                     @ переходим к следующему биту
                         LSL         R1, R1, 1
                     @ цикл передачи бит (снизу вверх)
                         SUBS        R7, R7, 1
                         BNE         LCD_pix_loop_12x16
                     @ цикл передачи линий (слева на право)
                         SUBS        R6, R6, 1
                         BNE         LCD_line_loop_12x16

                         BL          SPI_WAIT_TXE
                         bl          SPI_WAIT_BSY
                         POP         { R0, R1, R2, R4, R5, R6, R7, R8, PC }

@.DESC     name=LCD_CHAR_24x32 type=proc
@ ***************************************************************************
@ *                       Вывод символа по координатам                      *
@ ***************************************************************************
@ | Очистка области экрана и заливка цветом                                 |
@ | Параметры:                                                              |
@ |    LCD_CHAR: (R0:Y, R1:X, R2:Color, R3:Char) - native вывод символа     |
@ +-------------------------------------------------------------------------+
@.ENDDESC
.GLOBAL LCD_CHAR_24x32
LCD_CHAR_24x32:
                         PUSH        { R0, R1, R2, R4, R5, R6, R7, R8, LR }
                         @ рассчитаем адрес символа
                         LDR       R5, = LCD_CHARS_24x32
                         SUB       R3, R3, 32

                         MOV         R6, 96
                         MUL         R3, R3, R6
                         ADD         R3, R5, R3             @ в R3 адрес символа


                     @ Меняем X  Y  местами
                         MOV         R6, R0
                         MOV         R0, R1
                         MOV         R1, R6

                     @ Создадим область вывода
                         @ Упаковываем координату Y
                         PKHBT       R0, R0, R0, LSL 16
                         ADD         R0, R0, 23
                         @ Упаковываем координату X
                         PKHBT       R1, R1, R1, LSL 16
                         ADD         R1, R1, 31
                         BL          LCD_AT

                    @ R1 - 4 байта символа
                    @ R2 - color
                    @ R3 - адрес байта символа для вывода
                    @ R6 - счетчик строк символа для вывода
                    @ R7 - счетчик бит

                         MOV         R6, 24
LCD_line_loop_24x32:
                      @ передача одной строки символа
                         MOV         R7, 32

                         LDR         R1, [ R3 ], 4

LCD_pix_loop_24x32:
                     @ выбираем цвет точки
                         TST         R1, 0x80000000
                         ITE         EQ
                         UBFXEQ      R8, R2, 0, 16
                         UBFXNE      R8, R2, 16, 16
                     @ передаем цвет
                         UBFX        R5, R8, 8, 8
                         BL          LCD_SENDDATA
                         UBFX        R5, R8, 0, 8
                         BL          LCD_SENDDATA
                     @ переходим к следующему биту
                         LSL         R1, R1, 1
                     @ цикл передачи бит (снизу вверх)
                         SUBS        R7, R7, 1
                         BNE         LCD_pix_loop_24x32
                     @ цикл передачи линий (слева на право)
                         SUBS        R6, R6, 1
                         BNE         LCD_line_loop_24x32

                         BL          SPI_WAIT_TXE
                         bl          SPI_WAIT_BSY

                        @ BL          LCD_CS1

                         POP         { R0, R1, R2, R4, R5, R6, R7, R8, PC }

.section .rodata
.INCLUDE  "/src/devices/ST7789/font6x8.inc"                  @ шрифты для вывода
.INCLUDE "/src/devices/ST7789/font12x16.inc"
.INCLUDE "/src/devices/ST7789/font24x32.inc"


