@.CHARSET  CP1251

@ GNU AS
.SYNTAX   unified
.CPU      cortex-m3
.THUMB

.INCLUDE   "/src/inc/rcc.inc"
.INCLUDE   "/src/inc/gpio.inc"

@.DESC     name=LCD5110_Init type=proc
@ +---------------------------------------------------------------------------+
@ |          Процедура инициализации дисплея на контроллере PCD8544           |
@ +---------------------------------------------------------------------------+
@ | Модуль может использовать процедуры программного или аппаратного SPI      |
@ |                                                                           |
@ | Выбор используемого SPI осуществляется следующими настройками:            |
.EQU  spi_init      , SPI_TX_INIT      @ имя подпрограммы инициализации SPI   |
.EQU  spi_send      , SPI_TX_SEND      @ имя подпрограммы отправки данных     |
@ |                                    @ (отправляемые данные в R4)           |
.EQU  spi_wait      , SPI_TXE_BSY_WAIT @ процедура ожидание конца передачи    |
@ |                                                                           |
@ | Использование DMA при передачи при помощи hardware spi:                   |
.EQU  use_dma_transfer, Да               @                                    |
.EQU  dma_init      , DMA1_CH3_SPI_INIT  @ процедура инициализации DMA+SPI    |
.EQU  dma_send      , DMA1_CH3_SEND      @ процедура отправки буфера данных   |
.EQU  dma_wait      , DMA1_CH3_WAIT      @ процедура ожидания конца отправки  |
@ |                                                                           |
@ | подключение управляющих линий дисплея (RST, DC, CS) должно быть выполнено |
@ | к одному GPIO:                                                            |
.EQU  lcd_gpio_base , GPIOA_BASE         @ базовый (xxx_BASE) адрес gpio      |
.EQU  gpio_enr_bit  , RCC_APB2ENR_IOPAEN_N    @ номер бита в RCC_APB2ENR для  |
@ |                                        включения выбранного spi_gpio_base |
@ |                                                                           |
@ | подключение управляющих линий дисплея (RST, DC, CS) должно быть в рамках  |
@ | одного диапазона пинов GPIO: 0..7 или 8..15, иначе будет получена ошибка  |
@ | компиляции или будет некорректная работа модуля                           |
.EQU  rst_pin       , 8                  @ номер пина для RST сигнала дисплея |
.EQU  dc_pin        , 9                  @ номер пина для DC сигнала дисплея  |
.EQU  cs_pin        , 10                 @ номер пина для CS сигнала дисплея  |
@ |                                                                           |
@ | Вызовы модуля:                                                            |
@ | - LCD5110_INIT - Инициализация периферии микроконтроллера и дисплея       |
@ |            При вызове значения регистров микроконтроллера не сохраняются. |
@ |            Вызов не имеет параметров.                                     |
@ +---------------------------------------------------------------------------+
@ | видеоуроки про данный модуль:                                             |
@ |                                                                           |
@ | https://youtu.be/YjQJV2QvacQ                                              |
@ | ArmAsmEditor: STM32 на Ассемблере: Инициализация дисплея. Видео 15        |
@ |                                                                           |
@ | https://youtu.be/fxBM0pA7Ucw                                              |
@ | ArmAsmEditor: STM32 на Ассемблере: Графический примитив - точка. Работа   |
@ | с LCD 5110. Видео 17                                                      |
@ |                                                                           |
@ | https://youtu.be/6RA9iviSBw0                                              |
@ | ArmAsmEditor: STM32 на Ассемблере: Графический примитив - символ. Работа  |
@ | с LCD 5110. Видео 18                                                      |
@ +---------------------------------------------------------------------------+
@.ENDDESC

@.ITEM     APB2PERIPH_BASE_USER
.EQU  GPIOA_BASE         , APB2PERIPH_BASE + 0x0800  @ GPIO A
.EQU  GPIOB_BASE         , APB2PERIPH_BASE + 0x0C00  @ GPIO B
.EQU  GPIOC_BASE         , APB2PERIPH_BASE + 0x1000  @ GPIO C
.EQU  GPIOD_BASE         , APB2PERIPH_BASE + 0x1400  @ GPIO D
.EQU  GPIOE_BASE         , APB2PERIPH_BASE + 0x1800  @ GPIO E
.EQU  GPIOF_BASE         , APB2PERIPH_BASE + 0x1C00  @ GPIO F
.EQU  GPIOG_BASE         , APB2PERIPH_BASE + 0x2000  @ GPIO G
@.end

@.ITEM     RCC_APB2ENR_USER
.EQU  RCC_APB2ENR_IOPAEN_N, 2      @ GPIO A Enable Bit
.EQU  RCC_APB2ENR_IOPBEN_N, 3      @ GPIO B Enable Bit
.EQU  RCC_APB2ENR_IOPCEN_N, 4      @ GPIO C Enable Bit
.EQU  RCC_APB2ENR_IOPDEN_N, 5      @ GPIO D Enable Bit
.EQU  RCC_APB2ENR_IOPEEN_N, 6      @ GPIO E Enable Bit
.EQU  RCC_APB2ENR_IOPFEN_N, 7      @ GPIO F Enable Bit
.EQU  RCC_APB2ENR_IOPGEN_N, 8      @ GPIO G Enable Bit
@.END

@.CONFIG lcd_gpio_base=APB2PERIPH_BASE_USER  gpio_enr_bit=RCC_APB2ENR_USER

@.ITEM     use_dma_transfer_q
.EQU  Да            , 1             @ Использовать DMA
.EQU  Нет           , 0             @ Не использовать DMA
@.END

@.CONFIG   use_dma_transfer=use_dma_transfer_q

@ блок расчета значений для настроек GPIO
.IF        rst_pin > 7
.EQU  lcd_rst_pin   , rst_pin - 8
.EQU  lcd_dc_pin    , dc_pin - 8
.EQU  lcd_cs_pin    , cs_pin - 8
.EQU  gpio_crx      , GPIO_CRH                     @ имя регистра для настройки пинов: GPIO_CRH для пинов 8..15
.ELSE
.EQU  lcd_rst_pin   , rst_pin
.EQU  lcd_dc_pin    , dc_pin
.EQU  lcd_cs_pin    , cs_pin
.EQU  gpio_crx      , GPIO_CRL                     @ имя регистра для настройки пинов: GPIO_CRL для пинов 0..7
.ENDIF


.SECTION .asmcode

.GLOBAL   LCD5110_Init
LCD5110_Init:       @ настройка интерфейса передачи данных (spi)
                    PUSH       { LR }

                    BL         spi_init           @ настройка SPI интерфейса

.if use_dma_transfer==Да
                    BL         dma_init           @ инициализация DMA для хардварного SPI
.endif

                    @ значения для bitband операций
                    MOV        R1, 1
                    MOV        R0, 0

                    @ включим gpio используемый для линий управления lcd
                    LDR        R2, = PERIPH_BASE + PERIPH_BB_BASE + ( RCC_BASE + RCC_APB2ENR ) * 32
                    STR        R1, [ R2, gpio_enr_bit * 4 ]

                    @ настройка режима работы пинов GPIO
                    LDR        R2, = PERIPH_BASE + lcd_gpio_base
                    LDR        R4, [ R2, gpio_crx ]
                    MOV        R3, GPIO_MODE_OUT50
                    BFI        R4, R3, lcd_rst_pin * 4, 4
                    BFI        R4, R3, lcd_dc_pin * 4, 4
                    BFI        R4, R3, lcd_cs_pin * 4, 4
                    STR        R4, [ R2, gpio_crx ]

                    @ перевод пинов в первоначальное положение
                    LDR        R2, = PERIPH_BASE + PERIPH_BB_BASE + ( lcd_gpio_base + GPIO_ODR ) * 32
                    STR        R0, [ R2, dc_pin * 4 ]    @ DC=0  режим "команды"
                    STR        R0, [ R2, cs_pin * 4 ]    @ CS=0  "выбрать"

                    @ аппаратный сброс дисплея прижатием RST к нулю
                    STR        R0, [ R2, rst_pin * 4 ]   @ RST=0
                    BL         INIT_Pause
                    STR        R1, [ R2, rst_pin * 4 ]   @ RST=1
                    BL         INIT_Pause

                    @ отправляем последовательность команд для настройки дисплея
                    @ стр. 22 \Add\MCUDoc\LCD\Nokia5110.pdf
                    MOV        R4, 0x21
                    BL         spi_send

                    MOV        R4, 0x90
                    BL         spi_send

                    MOV        R4, 0x20
                    BL         spi_send

                    MOV        R4, 0x0C
                    BL         spi_send

                    @ здесь наш дисплей проинициализирован
                    STR        R1, [ R2, cs_pin * 4 ]    @ CS=1 - сообщаем дисплею что обмен с ним завершен

                    POP        { PC }


INIT_Pause:         @ процедура генерации задержки, используется при сбросе дисплея
                    MOV        R3, 0x1000
INIT_Pause_Loop:
                    SUBS       R3, R3, 1
                    BNE        INIT_Pause_Loop
                    BX         LR



@.DESC     name=LCD5110_REFRESH type=proc
@ +-------------------------------------------------------------------------+
@ |               Отправка буфера экрана из SRAM на LCD                     |
@ +-------------------------------------------------------------------------+
@ |                                                                         |
@ | Процедура  отправляет  содержимое  буфера  экрана  в  SRAM  на  LCD для |
@ | отображения                                                             |
@ |                                                                         |
@ | входные параметры: нет                                                  |
@ |                                                                         |
@ | выходные параметры: нет                                                 |
@ |                                                                         |
@ } изменяемые регистры по выходу: нет                                      |
@ |                                                                         |
@ +-------------------------------------------------------------------------+
@.ENDDESC

.SECTION .bss           @ буфер дисплея в SRAM
.align      ( 4 )
LCD_BUFF:               .space      84 * 6, 0

.SECTION .asmcode

.GLOBAL LCD5110_REFRESH
LCD5110_REFRESH:
@ очистка экрана (отладка)
                    PUSH       { R0, R1, R2, R3, R4, R5, LR }
                    MOV        R0, 0
                    MOV        R1, 1

                    LDR        R2, = PERIPH_BASE + PERIPH_BB_BASE + ( lcd_gpio_base + GPIO_ODR ) * 32

                    STR        R0, [ R2, cs_pin * 4 ]    @ CS=0 - сообщаем дисплею что будем с ним работать

                    STR        R0, [ R2, dc_pin * 4 ]    @ DC=0 - переходим в режим отправки команд

                    MOV        R4, 0x40           @ YPos=0
                    BL         spi_send

                    MOV        R4, 0x80           @ XPos=0
                    BL         spi_send

                    STR        R1, [ R2, dc_pin * 4 ]    @ DC=1 - переходим в режим отправки данных

                    MOV        R1, 84 * 6         @ количество отправляемых байт данных на дисплей
                    LDR        R0, = LCD_BUFF     @ адрес буфера экрана
.if use_dma_transfer==Да
                    BL         DMA1_CH3_SEND      @ старт отправки данных при помощи DMA

                    BL         DMA1_CH3_WAIT      @ подождем пока данные уйдут в SPI

                    BL         SPI_TXE_BSY_WAIT   @ подождем пока данные уйдут из SPI

.else               @ отправка данных в "ручном" режиме при помощи SPI
Refresh_loop:
                    LDRB       R4, [ R0 ], 1      @ загрузим байт с буфера, постинкремент
                    BL         spi_send           @ отправим байт по SPI

                    SUBS       R1, R1, 1          @ цикл пока не отправим все 84*6 байт из буфера
                    BNE        Refresh_loop       @
.endif
                    MOV        R1, 1
                    STR        R1, [ R2, cs_pin * 4 ]    @ CS=1 - сообщаем дисплею что обмен с ним завершен

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


@.DESC     name=LCD5110_CLEAR type=proc
@ +-------------------------------------------------------------------------+
@ |                      Очистка буфера дисплея                             |
@ +-------------------------------------------------------------------------+
@ |                                                                         |
@ | Процедура  очищает  буфер  экрана  в  ОЗУ, никаких данных ни дисплей не |
@ | отправляет (для этого используйте LCD5110_Refresh)                          |
@ |                                                                         |
@ | входные параметры: нет                                                  |
@ |                                                                         |
@ | выходные параметры: нет                                                 |
@ |                                                                         |
@ } изменяемые регистры по выходу: нет                                      |
@ |                                                                         |
@ +-------------------------------------------------------------------------+
@.ENDDESC

.GLOBAL LCD5110_CLEAR
LCD5110_CLEAR:
                    PUSH       { R0, R1, R2 }


                    MOV        R1, 84 * 6         @ количество байт  для записи

                    LDR        R2, = LCD_BUFF     @ адрес буфера
                    MOV        R0, 0x00           @ записываемое значение

Clear_loop:         @ обнуляем буфер записью по 4 байта (так быстрее)
                    STR        R0, [ R2 ], 4    @ постинкремент после записи в память

                    SUBS       R1, R1, 4
                    BNE        Clear_loop

                    POP        { R0, R1, R2 }
                    BX         LR


@.DESC     name=LCD5110_PIXEL type=proc
@ +-------------------------------------------------------------------------+
@ |              Рисование пиксела с заданными координатами                 |
@ +-------------------------------------------------------------------------+
@ |                                                                         |
@ | Процедура рисует пиксел с координатами в образе  изображения  в  буфере |
@ | экрана.                                                                 |
@ | Буфер на экран не отправляет, для этого используйте LCD5110_Refresh     |
@ |                                                                         |
@ | входные параметры:                                                      |
@ |    R0 - координата Y [0..47],                                           |
@ |    R1 - координата X [0..83],                                           |
@ |    R2 - цвет рисования: 1 - черная точка, 0 - стирание точки            |
@ |                                                                         |
@ | выходные параметры: нет                                                 |
@ |                                                                         |
@ } изменяемые регистры по выходу: нет                                      |
@ |                                                                         |
@ +-------------------------------------------------------------------------+
@.ENDDESC

.GLOBAL LCD5110_PIXEL
LCD5110_PIXEL:
                    PUSH       { R0, R1, R3, R4, R5 }

                    @ проверим допустимость координат вывода
                    CMP        R0, 48
                    BPL        LCD_PIXEL_exit
                    CMP        R1, 84
                    BPL        LCD_PIXEL_exit

                    @ вычисляем адрес пиксела
                    LSR        R3, R0, 3          @  y >> 3 - убрали инфу о бите позиции в байте
                    MOV        R4, 84
                    MUL        R3, R3, R4         @ (y >> 3) * 84      умножим строку на 84 пиксела в строке
                    ADD        R5, R3, R1         @ (y >> 3) * 84 + x  прибавим позицию Х

                    LDR        R4, = LCD_BUFF
                    ADD        R5, R5, R4         @ в R5 адрес бита

                    LDRB       R4, [ R5 ]         @ читаем байт содержащий нужный нам бит

                    @ вычисляем маску для наложения  бита в байте (младшие три бита позиции Y)
                    MOV        R3, 1
                    AND        R0, R0, 0x07       @ вычисляем номер бита в байте
                    LSL        R3, R3, R0

                    CMP        R2, 1              @ вывод пиксела
                    ITEE       EQ
                    ORREQ      R4, R4, R3         @ OR с маской
                    RSBNE      R3, R3, 255        @ инверсия маски
                    ANDNE      R4, R4, R3         @ AND с маской

                    STRB       R4, [ R5 ]         @ запись в буфер

LCD_PIXEL_exit:
                    POP        { R0, R1, R3, R4, R5 }
                    BX         LR

@.DESC     name=LCD5110_CHAR type=proc
@ +-------------------------------------------------------------------------+
@ |                               Вывод символа                             |
@ +-------------------------------------------------------------------------+
@ |                                                                         |
@ | Процедура рисует пиксел с координатами в образе  изображения  в  буфере |
@ | экрана.                                                                 |
@ | Буфер на экран не отправляет, для этого используйте LCD5110_Refresh     |
@ |                                                                         |
@ | входные параметры:                                                      |
@ |    R0 - координата Y [0..47],                                           |
@ |    R1 - координата X [0..83],                                           |
@ |    R2 - цвет (0: инверсия, 1: черный)                                   |
@ |    R3 - код выводимого символа                                          |
@ |                                                                         |
@ | выходные параметры: нет                                                 |
@ |                                                                         |
@ } изменяемые регистры по выходу: нет                                      |
@ |                                                                         |
@ +-------------------------------------------------------------------------+
@.ENDDESC

.INCLUDE  "/src/devices/pcd8544/font6x8.inc"                 @ файл знакогенератора

.GLOBAL LCD5110_CHAR
LCD5110_CHAR:
                         PUSH        { R0 - R12, LR }

                         CMP         R0, 48                  @ проверим допустимость координат
                         BPL         LCD_CHAR_exit
                         CMP         R1, 84
                         BPL         LCD_CHAR_exit

                         CMP         R2, 0                   @ проверка режима вывода символа
                         BNE         LCD_CHAR_noleftline     @ обычный вывод

                         CMP        R1, 0                    @ если вывод идет с начала строки
                         BEQ        LCD_CHAR_noleftline      @ то левую линию при инверсе не выводим

                      @ если выводим символ в инверсии
                         PUSH       { R0, R1, r2 }
                         MOV         R4, 9                   @ то рисуем слева от символа
                         SUB         R1, R1, 1               @ вертикальную линию
                         SUB         R0, R0, 1               @ чтобы символ не сливался
                         MOV        R2, 1
LCD_CHAR_leftline:
                         BL          LCD_ST7565R_PIXEL
                         ADD         R0, R0, 1
                         SUBS        R4, R4, 1
                         BNE         LCD_CHAR_leftline        @ цикл рисования линии
                         POP         { R0, R1, r2 }

LCD_CHAR_noleftline:
          @ пересчитаем код char для нашего знакогенератора
                         CMP         R3, 127
                         ITTEE       MI
                         ADRMI       R12, LCD_LAT_CHARS
                         SUBMI       R3, R3, 32
                         ADRPL       R12, LCD_RUS_CHARS   @ адрес знакогенератора
                         SUBPL       R3, R3, 192          @ номер символа в нашем знакогенераторе,

                         AND         R4, R0, 0x07            @ в R4 номер бита с которого надо выводить верх символа

                         LSR         R5, R0, 3               @ в R5 байтовая позиция по Y на экране

                   @ рассчитаем адрес байт-строки в буфере экрана
                         LDR         R9, = LCD_BUFF         @ расчет адреса в буфере экрана
                         MOV         R7, 84                 @ точек по горизонтали
                         MUL         R7, R7, R5             @ умножим на байтовую (с точностью до 8 точек) позицию по Y
                         ADD         R9, R9, R7
                         ADD         R9, R9, R1              @ адрес байта с 8 точками по вертикали в буфере экрана

           @ R0  : пиксельный Y
           @ R1  : пиксельный X
           @ R2  : признак инверсии
           @ R3  : номер символа в нашем знакогенераторе,
           @ R4  : номер бита с которого надо выводить верх символа
           @ R5  : байтовая позиция по Y на экране
           @ R9  : адрес в буфере экрана для вывода
           @ R12 : адрес знакогенератора
                         MOV         R6, 0                   @ счетчик цикла (число вертикальных столбцов символа = 6 [0..5])
LCD_CHAR_loop:
                         MOV         R7, 6                   @ расчитаем смещение для определения байт символа
                         MUL         R8, R3, R7              @ смещение (R8)= код символа(R3) * 6 (R7)

                         ADD         R8, R8, R6              @ прибавили номер столбца (R6) символа [0..6]
                         ADD         R8, R8, R12             @ прибавили адрес знакогенератора и получили в R8 адрес
                                                             @ байта- битового образа одного вертикального столбца пикселей
                         LDRB        R8, [ R8 ]              @ прочитаем столбец (байт символа)

                         CMP         R2, 0                   @ в зависимости от цвета
                         BNE         LCD_CHAR_noinv
                         RSB         R8, R8, 0xFF            @ инвертируем байт символа

                         CMP         R0, 0                   @ если Y=0 то
                         BEQ         LCD_CHAR_noinv          @ сверху точки не рисуем

                         PUSH        { R0, r2 }
                         SUB         R0, R0, 1               @ сверху вывод контрастной точки
                         MOV         R2, 1
                         BL          LCD_ST7565R_PIXEL               @ с фоном
                         POP         { R0, r2 }

LCD_CHAR_noinv:
                      @ вывод верхней части символа
                         LSL         R11, R8, R4            @ сдвинем наш образ строки символа влево на R10 бит
                         LDRB        R10, [ R9 ]             @ загрузим байт из буфера
                         ORR         R11, R10, R11           @ объединим его с нашим образом символа
                         STRB        R11, [ R9 ]             @ запишем байт

                       @ вывод нижней части символа
                         CMP         R4, 0                   @ проверяем нужно ли выводить
                         BEQ         LCD_CHAR_loop_end       @ нижнюю часть символа

                         CMP         R5, 5                   @ если экранные строки кончились
                         BEQ         LCD_CHAR_loop_end       @ то пропускаем их вывод

                         ADD         R7, R9, 84

                         RSB         r10, r4, 8
                         LSR         R11, R8, R10             @ сдвинем образ вправо

                         LDRB        R10, [ R7 ]              @ берем байт из буфера экрана
                         ORR         R11, R10, R11            @ накладываем наш образ символа
                         STRB        R11, [ R7 ]              @ сохраним байт в буфер экрана

LCD_CHAR_loop_end:
                         ADD         R1, R1, 1                @ X=X+1
                         CMP         R1, 84
                         BPL         LCD_CHAR_exit

                         ADD         R9, R9, 1                @ переходим к следующему байту в буфере экрана
                         ADD         R6, R6, 1                @ переходим к следующему столбцу бит символа
                         CMP         R6, 6                    @ если вывели все 6 то выходим
                         BNE         LCD_CHAR_loop            @ иначе цикл

LCD_CHAR_exit:
                         POP         { R0 - R12, PC }


