@.CHARSET  CP1251

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

.include  "/src/inc/i2c.inc"
.include  "/src/devices/SSD1306/ssd1306.inc"

.SECTION       .bss
.ALIGN         4
cmd_buf:            .SPACE          32
data_buf:           .SPACE          32
.global screen_buf
screen_buf:         .SPACE          SSD1306_BUFFER_SIZE

.SECTION       .asmcode

@.desc name=SSD1306_INIT type=proc
@ +--------------------------------------------------------------------------+
@ |                Инициализация контроллера дисплея SSD1306                 |
@ +--------------------------------------------------------------------------+
@ |  i2c к этому времени уже должен быть настроен                            |
@ +--------------------------------------------------------------------------+
@.enddesc

.global SSD1306_INIT
SSD1306_INIT:
                    PUSH            { R0, LR }

                    MOV             R0, SSD1306_SET_DISPLAY_OFF
                    BL              SSD1306_SendCommand

                    MOV             R0, SSD1306_SET_MUX_RATIO
                    BL              SSD1306_SendCommand
@if (SSD1306_HEIGHT == 32) высота дисплея 32 точки !
                    MOV             R0, 0x1F
                    BL              SSD1306_SendCommand     @ multiplex ratio value
@if (SSD1306_HEIGHT == 64) высота дисплея 64 точки !
@                    MOV             R0, 0x3F
@                    BL              SSD1306_SendCommand     @ multiplex ratio value
@if (SSD1306_HEIGHT == 128) высота дисплея 128 точек !
@                    MOV             R0, 0x3F
@                    BL              SSD1306_SendCommand     @ Seems to work for 128px high displays too.

@====================================================================
                    MOV             R0, SSD1306_SET_DISPLAY_OFFSET
                    BL              SSD1306_SendCommand
                    MOV             R0, 0x00
                    BL              SSD1306_SendCommand     @ not offset
@=============================================================================
                    MOV             R0, 0x40
                    BL              SSD1306_SendCommand     @ set display start line address
@=============================================================================
@                    MOV             R0, SSD1306_SET_MIRROR_HORIZONTALLY
@                    BL              SSD1306_SendCommand
                    MOV             R0, SSD1306_SET_NO_MIRROR_HORIZONTALLY
                    BL              SSD1306_SendCommand
@=============================================================================
@                    MOV             R0, SSD1306_SET_MIRROR_VERTICALLY
@                    BL              SSD1306_SendCommand
                    MOV             R0, SSD1306_SET_NO_MIRROR_VERTICALLY
                    BL              SSD1306_SendCommand
@=============================================================================
                    MOV             R0, SSD1306_SET_COM_PINS_HW_CONF
                    BL              SSD1306_SendCommand     @ set com pins hardware configuration
@#if (SSD1306_HEIGHT == 32)
                    MOV             R0, 0x02
                    BL              SSD1306_SendCommand
@#elif (SSD1306_HEIGHT == 64)
@                    MOV             R0, 0x12
@                    BL              SSD1306_SendCommand
@#elif (SSD1306_HEIGHT == 128)
@                    MOV             R0, 0x12
@                    BL              SSD1306_SendCommand
@#else
@#error "Only 32, 64, or 128 lines of height are supported!"
@#endif
@=============================================================================
                    MOV             R0, 0x30
                    BL              SSD1306_SetContrast          @ значение по умолчанию 0x7F
@=============================================================================
                    MOV             R0, 0xA4
                    BL              SSD1306_SendCommand     @ ssd1306_WriteCommand(0xA4); //0xa4,Output follows RAM content;0xa5,Output ignores RAM content
@=============================================================================
@                    MOV             R0, SSD1306_SET_INVERSE_COLOR
@                    BL              SSD1306_SendCommand
                    MOV             R0, SSD1306_SET_NORMAL_COLOR
                    BL              SSD1306_SendCommand
@=============================================================================
                    MOV             R0, 0xD5
                    BL              SSD1306_SendCommand     @ set display clock divide ratio/oscillator frequency
                    MOV             R0, 0x80
                    BL              SSD1306_SendCommand     @ ssd1306_WriteCommand(0xF0); // 0x80 --set divide ratio
@=============================================================================
                    MOV             R0, SSD1306_SET_MEMORY_ADDRESSING_MODE
                    BL              SSD1306_SendCommand
                    MOV             R0, SSD1306_HORIZONTAL_ADDRESSING_MODE
                    BL              SSD1306_SendCommand
@=====================================================================
@                    MOV             R0, 0xB0
@                    BL              SSD1306_SendCommand     @ Set Page Start Address for Page Addressing Mode,0-7
@=============================================================================
                    MOV             R0, SSD1306_SET_PRE_CHARGE_PERIOD
                    BL              SSD1306_SendCommand
                    MOV             R0, 0x22
                    BL              SSD1306_SendCommand
@=============================================================================
                    MOV             R0, SSD1306_SET_VCOMH
                    BL              SSD1306_SendCommand
                    MOV             R0, SSD1306_077xVCC_VCOMH        @ значение по умолчанию при старте
                    BL              SSD1306_SendCommand
@=============================================================================
                    MOV             R0, SSD1306_SET_DCDC_ENABLE
                    BL              SSD1306_SendCommand
                    MOV             R0, 0x14                @ 010100 = 0x14 - enable pump, 010000 = 0x10 - disable pump
                    BL              SSD1306_SendCommand
@=============================================================================
                    MOV             R0, SSD1306_SET_DISPLAY_ON
                    BL              SSD1306_SendCommand
@=============================================================================
@                    MOV             R0, SSD1306_SET_COLUMN_ADDRESS
@                    BL              SSD1306_SendCommand
@                    MOV             R0, 0x00               @ значение по умолчанию 0
@                    BL              SSD1306_SendCommand
@                    MOV             R0, SSD1306_X_SIZE - 1 @ значение по умолчанию 127
@                    BL              SSD1306_SendCommand
                    MOV             R0, SSD1306_SET_PAGE_ADDRESS @ задать пределы счёта для счётчика страниц – от 0 до 3 =>  0x22,0,3
                    BL              SSD1306_SendCommand
                    MOV             R0, 0x00
                    BL              SSD1306_SendCommand     @ set low page address
                    MOV             R0, 0x03                @ по умолчанию значение 0x07 даже для дисплея 32x128
                    BL              SSD1306_SendCommand     @ set high page address
@========================================================================

                    MOV             R0, 100
                    BL              SYSTICK_DELAY

                    BL              SSD1306_ClearScreen

                    POP             { R0, PC }

@.desc name=SSD1306_SendCommand type=proc
@ ****************************************************************************
@ *  Входные данные:                                                         *
@ *  R0 - дата для отправки                                                  *
@ ****************************************************************************
@.enddesc
.global SSD1306_SendCommand
SSD1306_SendCommand:
                    PUSH            { R2 - R12, LR }
                    LDR             R11, =data_buf
                    STRB            R0, [ R11 ]
                    MOV             R7, 1    @ кол-во байт для отправки
                    MOV             R8, SSD1306_I2C_ADDRESS
                    MOV             R12, SSD1306_I2C_CONTROL_BYTE_COMMAND
                    LDR             R2, = I2C1 + I2C_DR
                    LDR             R3, = PERIPH_BB_BASE + ( I2C1 + I2C_CR1 ) * 32
                    LDR             R4, = PERIPH_BB_BASE + ( I2C1 + I2C_SR1 ) * 32
                    LDR             R10, = PERIPH_BB_BASE + ( I2C1 + I2C_SR2 ) * 32
                    LDR             R5, = I2C1 + I2C_SR2
                    LDR             R6, = I2C1 + I2C_SR1
                    BL              I2C_Mem_Write
                    POP             { R2 - R12, PC }

@.desc name=SSD1306_SendData type=proc
@ ****************************************************************************
@ *  Входные данные:                                                         *
@ *  R11 - Адрес в памяти с данными для отправки                             *
@ *  R7 - Количество байт для отправки                                       *
@ ****************************************************************************
@.enddesc
.global SSD1306_SendData
SSD1306_SendData:
                    PUSH            { R2 - R12, LR }

                    MOV             R8, SSD1306_I2C_ADDRESS
                    MOV             R12, SSD1306_I2C_CONTROL_BYTE_DATA
                    LDR             R2, = I2C1 + I2C_DR
                    LDR             R3, = PERIPH_BB_BASE + ( I2C1 + I2C_CR1 ) * 32
                    LDR             R4, = PERIPH_BB_BASE + ( I2C1 + I2C_SR1 ) * 32
                    LDR             R10, = PERIPH_BB_BASE + ( I2C1 + I2C_SR2 ) * 32
                    LDR             R5, = I2C1 + I2C_SR2
                    LDR             R6, = I2C1 + I2C_SR1
                    BL              I2C_Mem_Write

                    POP             { R2 - R12, PC }

@.desc name=SSD1306_SetContrast type=proc
@ ****************************************************************************
@ *  Входные данные:                                                         *
@ *  R0 - значение контраста                                                 *
@ ****************************************************************************
@.enddesc
.global SSD1306_SetContrast
SSD1306_SetContrast:
                    PUSH            { R1, R2, LR }
                    MOV             R2, R0
                    MOV             R0, SSD1306_SET_CONTRAST_CONTROL_REGISTER
                    BL              SSD1306_SendCommand
                    MOV             R0, R2
                    BL              SSD1306_SendCommand
                    POP             { R1, R2, PC }

@.desc name=SSD1306_ClearScreen type=proc
@ ****************************************************************************
@ *  Входные данные:                                                         *
@ *  R0 -                                                                    *
@ ****************************************************************************
@.enddesc
.global SSD1306_ClearScreen
SSD1306_ClearScreen:
                    PUSH            { R0, R1, R2, LR }

                    MOV             R2, SSD1306_BUFFER_SIZE/4
                    MOV             R0, 0
                    LDR             R1, = screen_buf
next_byte_cs:
                    STR             R0, [ R1 ], 4           @ for (i = 0; i < SSD1306_BUFFER_SIZE; i++) pixelBuffer[i] = 0x00;
                    SUBS            R2, R2, 1
                    BNE             next_byte_cs

@                    BL              SSD1306_UpdateScreen
                    POP             { R0, R1, R2, PC }

@.desc name=SSD1306_SetPixel type=proc
@ ****************************************************************************
@ *  Входные данные:                                                         *
@ *  R0 - y                                                                  *
@ *  R1 - x                                                                  *
@ *  R2 - color (0 - черный, не 0 - белый)                                   *
@ *  screen_buf:         .SPACE          SSD1306_BUFFER_SIZE                 *
@ ****************************************************************************
@.enddesc
.global SSD1306_SetPixel
SSD1306_SetPixel:
                    PUSH            { R0, R1, R3, R4, R5, LR }
                    CMP             R0, SSD1306_Y_SIZE
                    BCS             exit_sp
                    CMP             R1, SSD1306_X_SIZE
                    BCS             exit_sp
                                                       @ pixelBuffer[x + (y / 8) * SSD1306_X_SIZE] |= (1 << (y % 8));
                    MOV             R5, SSD1306_X_SIZE
                    AND             R4, R0, 0b111      @ R4 = y % 8                            @ в R4 остаток от деления на 8
                    LSR             R3, R0, 3          @ R3 = y / 8
                    MLA             R3, R3, R5, R1     @ R3 = (y / 8) * SSD1306_X_SIZE + X     @ в R3 теперь смещение байта в массиве screen_buf

                    LDR             R1, = screen_buf
                    ADD             R3, R1, R3                                                 @ в R3 адрес байта

                    LDRB            R5, [ R3 ]         @ R5 - байт из массива

                    MOV             R1, 1
                    LSL             R1, R1, R4         @ R1 = 1 << (y % 8)                @ в R1 значение для установки бита   @ например 0b00001000
                    MVN             R0, R1             @ R0 = NOT(R1)                     @ в R0 значение для сброса бита      @ например 0b11110111

                    CMP             R2, 0              @ черный или белый пиксель?
                    ITE             EQ
                    ANDEQ           R5, R5, R0         @ сброс бита
                    ORRNE           R5, R5, R1         @ установка бита

                    STRB            R5, [ R3 ]         @ запись результата в массив

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

@.desc name=SSD1306_DrawFilledRect type=proc
@ ****************************************************************************
@ *  Входные данные:                                                         *
@ *  R0 -                                                                    *
@ ****************************************************************************
@.enddesc
.global SSD1306_DrawFilledRect
SSD1306_DrawFilledRect:
                    PUSH            { R1, R2, LR }
@screen_buf:         .SPACE          SSD1306_BUFFER_SIZE
@void SSD1306_DrawFilledRect(uint8_t xStart, uint8_t xEnd, uint8_t yStart, uint8_t yEnd)
@  for (uint8_t i = xStart; i < xEnd; i++) for (uint8_t j = yStart; j <  yEnd; j++) SetPixel(i, j);
                    POP             { R1, R2, PC }

@.desc name=SSD1306_UpdateScreen type=proc
@ ****************************************************************************
@ * Процедура копирует данные из screen_buf в дисплей.                       *
@ * Для дисплея 32x128 это 512 байт                                          *
@ ****************************************************************************
@.enddesc
.global SSD1306_UpdateScreen
SSD1306_UpdateScreen:
                    PUSH            { R7, R11, LR }
                    LDR             R11, = screen_buf
                    MOV             R7, SSD1306_BUFFER_SIZE
                    BL              SSD1306_SendData
                    POP             { R7, R11, PC }


@.DESC     name=SSD1306_LCD_CHAR_6x8_text_mode type=proc
@ ***************************************************************************
@ *   Вывод символа по координатам в условно текстовом режиме.              *
@ * Координата Y определяет строку (для дисплея 32х128 всего 4 строки)      *
@ * Координата X определяет позицию в строке (всего 128 - ширина символа    *
@ * позиций)                                                                *
@ ***************************************************************************
@ | Параметры:                                                              |
@ |    LCD_CHAR: (R0:Y, R1:X, R2:Color, R3:Char) - native вывод символа     |
@ +-------------------------------------------------------------------------+
@.ENDDESC
.GLOBAL SSD1306_LCD_CHAR_6x8_text_mode
SSD1306_LCD_CHAR_6x8_text_mode:
                    PUSH            { R0, R1, R2, R4, R5, R6, R7, R8, LR }
                    CMP             R0, SSD1306_Y_SIZE
                    BCS             exit_1_tm
                    CMP             R1, SSD1306_X_SIZE-6
                    BCS             exit_1_tm

                     @ рассчитаем адрес символа
                    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 адрес символа

                    LDR             R7, = screen_buf   @ расчет начальной позиции для символа в текстовом режиме: pos_in_buf = nLine * 128 + X
                    LSR             R0, R0, 3          @ / 8
                    LSL             R0, R0, 7          @ * 128
                    ADD             R0, R0, R1         @ + X
                    ADD             R7, R7, R0

                    MOV             R6, 6              @ R6 - счетчик строк символа для вывода
LCD_line_loop_tm:
                    LDRB            R1, [ R3 ], 1      @ R3 - адрес байта символа для вывода
                    STRB            R1, [ R7 ], 1      @ R7 - адрес в screen_buf

                    SUBS            R6, R6, 1    @ цикл передачи вертикальных линий (слева на право)
                    BNE             LCD_line_loop_tm

exit_1_tm:

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

@.DESC     name=SSD1306_LCD_CHAR_6x8 type=proc
@ ***************************************************************************
@ *                       Вывод символа по координатам.                     *
@ ***************************************************************************
@ | Параметры:                                                              |
@ | (R0:Y, R1:X, R2:Color (параметр игнорируется), R3:Char)                 |
@ +-------------------------------------------------------------------------+
@.ENDDESC
.GLOBAL SSD1306_LCD_CHAR_6x8
SSD1306_LCD_CHAR_6x8:
                    PUSH            { R0, R1, R2, R4, R5, R6, R7, R8, LR }

                    CMP             R0, SSD1306_Y_SIZE
                    BCS             exit_c
                    CMP             R1, SSD1306_X_SIZE-6
                    BCS             exit_c

                     @ рассчитаем адрес символа
                    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 адрес символа
                                                            @ (R0, R1) - абсолютные (Y, X) координаты начала символа
                    MOV             R6, 0                   @ R6 - счетчик столбцов (X) (отдельные байты в массиве шрифта, в буфере экрана это относительная Y координата столбца)
LCD_line_loop_gm:
                    LDRB            R5, [ R3 ], 1           @ R3 - адрес байта символа для вывода   @ R5 - байт символа
                    MOV             R7, 8                   @ R7 - счетчик строк (Y) (отдельные биты в текущем символе, в буфере экрана это относительная Х координата строки)
                    PUSH            { R0 }
LCD_bite_loop_gm:
@                         TST             R5, 0x01
@                         ITE             EQ
@                         MOVEQ           R2, 0
@                         MOVNE           R2, 1              @ В R2 - Цвет точки из символа в шрифте
                         AND             R2, R5, 1          @ 1 команда вместо 4-х     @ В R2 - Цвет точки из символа в шрифте
                         BL              SSD1306_SetPixel   @ (R0 - Y, R1 - X, R2 - цвет)
                         ADD             R0, R0, 1          @ R0  Y
                         LSR             R5, R5, 1          @ переходим к следующему биту
                         SUBS            R7, R7, 1          @ побитовый цикл
                         BNE             LCD_bite_loop_gm
                    POP             { R0 }
                    ADD             R1, R1, 1
                    ADD             R6, R6, 1          @ цикл передачи вертикальных линий (слева на право)
                    CMP             R6, 6
                    BNE             LCD_line_loop_gm
exit_c:
                    POP             { R0, R1, R2, R4, R5, R6, R7, R8, PC }



.SECTION       .rodata
.INCLUDE  "/src/devices/SSD1306/font6x8.inc"



