@.CHARSET CP1251

@ GNU AS
.syntax unified
.cpu cortex-m4
.thumb
.fpu fpv4-sp-d16

@.desc name=SSD1306 type=module
@ +------------------------------------------------------------------------+
@ |         Драйвер i2c дисплея на SSD1306 с разрешением 128х64            |
@ +------------------------------------------------------------------------+
@ | Не путать с SH1107 с разрешением 128х64 !!! это разные дисплеи !!!     |
@ +------------------------------------------------------------------------+

.SECTION       .asmcode

@.desc name=LCD_SSD1306_INIT type=proc
@ +------------------------------------------------------------------------+
@ |                Инициализация   OLED LCD SSD1306 (128x64)               |
@ +------------------------------------------------------------------------+
@ |                                                                        |
.equ LCD_ADR_WR, 0x78            @ адрес дисплея на запись                 |
@ |                                                                        |
@ | Используемые процедуры:                                                |
.EQU  I2C_ADR       , I2C_TARGET_BEGIN    @ процедура адресации дисплея    |
.EQU  I2C_SEND      , I2C_SEND_BYTE       @ отправка байта по i2c          |
@ |                                                                        |
@ +------------------------------------------------------------------------+
.global LCD_SSD1306_INIT
LCD_SSD1306_INIT:
                    PUSH            { R0, R1, R2, R3, LR }

                    @ адресуем дисплей
                    MOV             R0, LCD_ADR_WR
                    BL              I2C_ADR

                    @ инициализация дисплея
                    MOV             R2, SSD1306_init_endtable-SSD1306_init_seq    @ количество байт
                    LDR             R3, =SSD1306_init_seq                         @ буфер для отправки

send_loop:          @ цикл инициализации
                    MOV             R0, 0x80    @ управляющий байт
                    MOV             R1, 0
                    BL              I2C_SEND

                    LDRB            R0, [ R3 ], 1     @ команда
                    CMP             R2, 1             @ если это последний отправляемый байт
                    IT              EQ
                    MOVEQ           R1, 1             @ - то завершим диалог отправив STOP
                    BL              I2C_SEND

                    SUBS            R2, R2, 1
                    BNE             send_loop

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

.section .rodata
.align 1
SSD1306_init_seq:
                    .BYTE           0xAE    @ Выключить дисплей

                    .BYTE           0xD5    @ Настройка частоты обновления дисплея
                    .BYTE           0x80
                                  @    +----- делитель 0-F/ 0 - деление на 1
                                  @   +------ частота генератора. по умочанию 0x80

                    .BYTE           0xD9    @ Настройка фаз DC/DC преоразователя
                    .BYTE           0xF1    @ 0x22 - VCC подается извне / 0xF1 для внутренего

                    .BYTE           0xDB    @ Установка уровня VcomH
                    .BYTE           0x70    @ Влияет на яркость дисплея 0x00..0x70

                    .BYTE           0x8D    @ Управление внутреним преобразователем
                    .BYTE           0x14    @ 0x10 - отключить ( VCC подается извне ) 0x14 - запустить внутрений DC / DC

                    .BYTE           0xA8    @ Установить multiplex ratio
                    .BYTE           0x3F    @ 1 / 64 duty ( значение по умолчанию ), 0x1F - 128x32, 0x3F - 128x64

                    .BYTE           0xDA    @ Аппаратная конфигурация COM
                    .BYTE           0x12    @ 0x02 - 128x32, 0x12 - 128x64

                    .BYTE           0xD3    @ смещение COM
                    .BYTE           0x00    @

                    .BYTE           0xC8    @ направление вывода X0 Y0 слева вверху рядом с разъемом
                    .BYTE           0xA1    @ направление вывода

                    .BYTE           0x40    @ начинать с com0 начала озу

                    .BYTE           0x81    @  Установка яркости дисплея
                    .BYTE           0xCF    @ 0x8F..0xCF

                    .BYTE           0xA6    @ без инверсии

                    .BYTE           0xA4    @ нормальная работа

                    .BYTE           0xAF    @  Дисплей включить
SSD1306_init_endtable:


@.desc name=LCD_SSD1306_CLEAR type=proc
@ +--------------------------------------------------------------------+
@ |                         Очистка буфера экрана                      |
@ +--------------------------------------------------------------------+
@ |  Регистры значения не меняют                                       |
@ +--------------------------------------------------------------------+
.SECTION       .bss
lcd_buff:           .SPACE          8 * 128 @ буфер дисплея

.section .asmcode
.global LCD_SSD1306_CLEAR
LCD_SSD1306_CLEAR:
                    PUSH            { R0, R1, R2 }
                    LDR             R2, = lcd_buff
                    MOV             R0, 0x00
                    MOV             R1, 8 * 128
clear_loop:
                    STRB            R0, [ R2 ], 1

                    SUBS            R1, R1, 1
                    BNE             clear_loop

                    POP             { R0, R1, R2 }
                    BX              LR


@.desc name=LCD_SSD1306_REFRESH type=proc
@ +--------------------------------------------------------------------+
@ |                         Очистка буфера экрана                      |
@ +--------------------------------------------------------------------+
@ |  Регистры значения не меняют                                       |
@ +--------------------------------------------------------------------+
.global LCD_SSD1306_REFRESH
LCD_SSD1306_REFRESH:
                    PUSH            { R0, R1, R2, R3, R4, LR }

                    @ адресуем дисплей
                    LDR             R2, = lcd_buff   @ адрес буфера для вывода
                    MOV             R3, 0           @ номер заполняемой страницы

pages_loop:         @ цикл отправки данных по вертикальным pages
                    MOV             R0, LCD_ADR_WR
                    MOV             R1, 0
                    BL              I2C_ADR

                    MOV             R0, 0x80        @ управляющий байт
                    BL              I2C_SEND

                    ADD             R0, R3, 0xB0    @ позиционирование по pages
                    BL              I2C_SEND

                    MOV             R0, 0x80        @ управляющий байт
                    BL              I2C_SEND

                    MOV             R0, 0x00        @ позиционирование com .xxxx
                    BL              I2C_SEND

                    MOV             R0, 0x80        @ управляющий байт
                    BL              I2C_SEND

                    MOV             R0, 0x10        @ позиционирование com x....
                    BL              I2C_SEND

                    MOV             R4, 128      @ 128 байт в одной page

send_loop1:         @ цикл отправки изображения
                    MOV             R0, 0xC0    @ управляющий байт
                    BL              I2C_SEND

                    LDRB            R0, [ R2 ], 1    @ байт изображения
                    CMP             R4, 1
                    IT              EQ
                    MOVEQ           R1, 1
                    BL              I2C_SEND

                    SUBS            R4, R4, 1     @ цикл передачи одного page
                    BNE             send_loop1

                    ADD             R3, R3, 1     @ цикл передачи всех pages
                    CMP             R3, 8
                    BNE             pages_loop

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

.INCLUDE  "/src/devices/ssd1306/font8x10.inc"

@.desc LCD_SSD1306_CHAR type=proc
@ +--------------------------------------------------------------------+
@ |                             Вывод символа                          |
@ +--------------------------------------------------------------------+
@ |  Регистры значения не меняют                                       |
@ +--------------------------------------------------------------------+
@ |  R0 - Y (0..53)                                                    |
@ |  R1 - X (0..119)                                                   |
@ |  R2 - color (0 - простой вывод, 1 - негатив)                       |
@ |  R3 - Symbol  (cp1251)                                             |
@ +--------------------------------------------------------------------+
@ |
.global LCD_SSD1306_CHAR
LCD_SSD1306_CHAR:
                    CMP             R0, 54
                    IT              HI
                    BXHI            LR

                    PUSH            { R0, R1, R3, R4, R5, R6, R7, R8, LR }

                     @ рассчитаем адрес символа
                    LDR             R5, = font8x10
                    SUB             R3, R3, 32           @ уменьшим код символа на 32

                    MOV             R6, 16               @ размер символа в байтах
                    MUL             R3, R3, R6
                    ADD             R3, R5, R3           @ в R3 адрес символа
                    @ в R3 адрес символа
                    LSR             R4, R0, 3            @ page для вывода по Y
                    ADD             R5, R1, R4, LSL 7    @ расчитаем относительный адрес начала символа
                    LDR             R4, = lcd_buff
                    ADD             R5, R5, R4           @ абсолютный адрес в буфере дисплея
                    @ в R5 адрес символа в буфере


                    AND             R0, R0, 0x07         @ битовая позиция символа в байте
                    RSB             R7, R0, 8      @ значение для сдвига

                    mov r1, 8
char_loop:
                    LDRH            R6, [ R3 ], 2

                    CMP             R2, 1
                    ITTT            EQ
                    MOVEQ           R8, 0b0000001111111111
                    SUBEQ           R6, R8, R6
                    ANDEQ           R6, R6, R8

                    LSL             R4, R6, R0

                    LDRB            R8, [ R5 ] @ верхняя  часть символа
                    ORR             R8, R4, R8
                    STRB            R8, [ R5 ]

                    LSR             R4, R4, 8       @ средняя часть символа
                    LDRB            R8, [ R5, 128 ]
                    ORR             R8, R4, R8
                    STRB            R8, [ R5, 128 ]

                    LSR             R4, R4, 8       @ нижняя часть символа
                    LDRB            R8, [ R5, 256 ]
                    ORR             R8, R4, R8
                    STRB            R8, [ R5, 256 ]
                    ADD             R5, R5, 1

                    SUBS            R1, R1, 1
                    BNE             char_loop

                    pop            { R0, R1, R3, R4, R5, R6, R7, R8, PC }


@.desc LCD_SSD1306_PIXEL type=proc
@ +--------------------------------------------------------------------+
@ |                        Вывод точки                                 |
@ +--------------------------------------------------------------------+
@ |  Регистры значения не меняют                                       |
@ +--------------------------------------------------------------------+
@ |  R0 - Y                                                            |
@ |  R1 - X                                                            |
@ |  R2 - color (0 - простой вывод, 1 - стирание)                      |
@ +--------------------------------------------------------------------+
.global LCD_SSD1306_PIXEL
LCD_SSD1306_PIXEL:
                    PUSH            { R0, R1, R3, R4, R5, LR }

                    LSR             R4, R0, 3            @ позиция по Y в байтах
                    ADD             R5, R1, R4, LSL 7    @ расчитаем относительный адрес начала символа
                    LDR             R4, = lcd_buff
                    ADD             R5, R5, R4           @ абсолютный адрес в буфере дисплея

                    AND             R0, R0, 0x07         @ битовая позиция символа в байте

                    @ R1 - битовая позиция символа в байте
                    @ R2 - режим вывода (0 - orr, `1 - neg+and)
                    @ R5 - адрес в буфере

                    MOV             R1, 1
                    LSL             R0, R1, R0

                    LDRB            R4, [ R5 ]      @ загрузим байт из буфера

                    CMP             R2, 0
                    ITEE            EQ
                    ORREQ           R0, R4, R0    @ наложим на байт из буфера
                    RSBNE           R0, R0, 0xFF
                    ANDNE           R0, R0, R4

                    STRB            R0, [ R5 ]      @ сохраним в буфер

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


@.desc LCD_SSD1306_BRIGHT type=proc
@ +--------------------------------------------------------------------+
@ |                     Установка яркости                              |
@ +--------------------------------------------------------------------+
@ |  Регистры значения не меняют                                       |
@ +--------------------------------------------------------------------+
@ |  R2 - Значениея яркости (0x40..0xCF)                               |
@ | с превышением значения яркости (0xCF) осторожнее, у меня прожгло   |
@ | одну строку пикселов :-(                                           |
@ +--------------------------------------------------------------------+
.global LCD_SSD1306_BRIGHT
LCD_SSD1306_BRIGHT:
                    PUSH            { R0, R1, LR }
                    MOV             R0, LCD_ADR_WR
                    MOV             R1, 0
                    BL              I2C_ADR

                    MOV             R0, 0x80        @ управляющий байт
                    BL              I2C_SEND

                    MOV             R0, 0x81
                    BL              I2C_SEND

                    MOV             R0, 0x80        @ управляющий байт
                    BL              I2C_SEND

                    MOV             R0, R2
                    MOV             R1, 1
                    BL              I2C_SEND

                    POP             { R0, R1, PC }



