@.CHARSET CP1251

@ +---------------------------------------------------------+
@ |  Драйвер i2c c минимальным набором сервисных процедур   |
@ +---------------------------------------------------------+
@ | Автор кода: Александр Епифанцев                         |
@ |          https://t.me/ArmAsmEditor/18934                |
@ +---------------------------------------------------------+

@ GNU AS
.syntax unified
.cpu cortex-m4
.thumb

.include  "/src/inc/i2c.inc"
.include  "/src/inc/rcc.inc"

.section .asmcode

.EQU  APB1_FREQ_MHZ , 42

.EQU  MAX_CICLE_DELAY, 2000 * 1024    @ ~1,3s 100MHz, ~5s (1000 * 1024)

.EQU  RX            , 1
.EQU  TX            , 0

.EQU  I2C_NO_ERROR  , 0
.EQU  I2C_BUSY      , 1
.EQU  I2C_START_ERROR, 2
.EQU  I2C_CRC_ERROR , 3

@                                                                                                                        BUSY
@                                                                                                                     TRA|MSL         TxE    ADDR
@ /* --EV6 */                                                                                                           |||           |      |
@ #define I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED ((uint32_t)0x00070082) /* BUSY, MSL, ADDR, TXE and TRA flags */   0111 0000 0000 1000 0010
@ #define I2C_EVENT_MASTER_RECEIVER_MODE_SELECTED    ((uint32_t)0x00030002) /* BUSY, MSL and ADDR flags */             0011 0000 0000 0000 0010
@                                                                                                               {   ...SR2} {       SR1       }
@

@ .EQU I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED, 0x00070082 @ /* BUSY, MSL, ADDR, TXE and TRA flags */   0111 0000 0000 1000 0010
@ .EQU I2C_EVENT_MASTER_RECEIVER_MODE_SELECTED,    0x00030002 @ /* BUSY, MSL and ADDR flags */             0011 0000 0000 0000 0010

@ упрощенно: FIXME
.EQU I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED, 0x00000082 @ /* BUSY, MSL, ADDR, TXE and TRA flags */   0111 0000 0000 1000 0010
.EQU I2C_EVENT_MASTER_RECEIVER_MODE_SELECTED,    0x00000002 @ /* BUSY, MSL and ADDR flags */             0011 0000 0000 0000 0010


config_table:
@ 1
.word PERIPH_BB_BASE + ( I2C1 + I2C_OAR2 ) * 32, I2C1 + I2C_OAR1,  I2C1 + I2C_CCR, I2C1 + I2C_TRISE
.word I2C1 + I2C_CR2, PERIPH_BB_BASE + ( I2C1 + I2C_CR1 ) * 32, PERIPH_BB_BASE + ( RCC_BASE + RCC_APB1ENR ) * 32 + RCC_APB1ENR_I2C1EN_N * 4
.word I2C1 + I2C_OAR2

@ 2
.word PERIPH_BB_BASE + ( I2C2 + I2C_OAR2 ) * 32, I2C2 + I2C_OAR1, I2C2 + I2C_CCR, I2C2 + I2C_TRISE
.word I2C2 + I2C_CR2, PERIPH_BB_BASE + ( I2C2 + I2C_CR1 ) * 32, PERIPH_BB_BASE + ( RCC_BASE + RCC_APB1ENR ) * 32 + RCC_APB1ENR_I2C2EN_N * 4
.word I2C2 + I2C_OAR2

@ 3
.word PERIPH_BB_BASE + ( I2C3 + I2C_OAR2 ) * 32, I2C3 + I2C_OAR1, I2C3 + I2C_CCR, I2C3 + I2C_TRISE
.word I2C3 + I2C_CR2, PERIPH_BB_BASE + ( I2C3 + I2C_CR1 ) * 32, PERIPH_BB_BASE + ( RCC_BASE + RCC_APB1ENR ) * 32 + RCC_APB1ENR_I2C3EN_N * 4
.word I2C3 + I2C_OAR2

@.desc name=I2C_INIT type=proc
@ ****************************************************************************
@ *                            Инициализация I2C                             *
@ * Входной параметр:                                                        *
@ * R0 - номер I2C                                                           *
@ ****************************************************************************
@.enddesc
.global I2C_INIT
I2C_INIT:
                    PUSH            { R0 - R12, LR }

                    SUB             R0, R0, 1
                    MOV             R1, 8*4
                    MUL             R0, R0, R1
                    ADR             R1, config_table
                    ADD             R1, R0, R1

                    LDM             R1 !, { R2 - R9 }

                    MOV             R0, 0
                    MOV             R1, 1
                    STR             R1, [ R8]          @ включение тактирования I2C

                   STR             R0, [ R7, I2C_CR1_PE_N * 4 ]      @ отключается модуль I2C
@                   STR             R0, [ R7, I2C_CR1_ENGC_N * 4 ]    @ отключается работа с широковещательными запросами    @  hi2c1.Init.GeneralCallMode = I2C_GENERALCALL_DISABLE;
@                   STR             R0, [ R7, I2C_CR1_NOSTRETCH_N * 4]@ отключается увеличение времени в режиме SLAVE        @  hi2c1.Init.NoStretchMode = I2C_NOSTRETCH_DISABLE;
@                   STR             R0, [ R7, I2C_CR1_SMBUS_N * 4]    @ режим работы шины 0: I2C mode
@                   STR             R0, [ R7, I2C_CR1_ENARP_N * 4]
@                   STR             R0, [ R7, I2C_CR1_SMBTYPE_N * 4]

                    MOV             R10, APB1_FREQ_MHZ / 2
@                    STRB            R12, [ R3 ]                       @ set I2C_OAR1
                    LDR             R11, [ R3 ]
                    BFI             R11, R10, 1, 7
                    STR             R11, [ R3 ]                       @ set I2C_OAR1

                    STRB            R0, [ R9 ]                        @ set I2C_OAR2
                    STR             R0, [ R2, I2C_OAR2_ENDUAL_N * 4]  @ отключается режим двойной адресации       @  hi2c1.Init.DualAddressMode = I2C_DUALADDRESS_DISABLE;
                    @ регистры R2, R3, R9 - свободны

@ Установки для частоты i2c 100 кГц и тактовой  APB1 25 МГц (SM mode):
                    STR             R0, [ R4, I2C_CCR_FS_N * 4 ]    @ 0 - Sm mode, 1 - Fm mode
                    STR             R0, [ R4, I2C_CCR_DUTY_N * 4]   @ 0 - Fm mode tlow/thigh = 2, 1 - 16/9      @  hi2c1.Init.DutyCycle = I2C_DUTYCYCLE_2;

                    MOV             R10, APB1_FREQ_MHZ            @ 25 МГц APB1

                    MOV             R11, 5             @ 5us
                    MUL             R11, R11, R10      @ 5000нс / 40 нс = 125 (40 нс - период для частоты 25 МГц APB1)

                    LDR             R12, [ R4 ]
                    BFI             R12, R11, 0, 12
                    STR             R12, [ R4 ]        @ set CCR

                    MOV             R11, R10

                    LDR             R12, [ R6 ]
                    BFI             R12, R11, 0, 6
                    STR             R12, [ R6 ]        @ set I2C_CR2

                    MOV             R11, R10
                    ADD             R11, R11, 1        @ (1000нс / 40 нс) + 1 = 26

                    LDR             R12, [ R5 ]
                    BFI             R12, R11, 0, 6
                    STR             R12, [ R5 ]         @ set I2C_TRISE

                    STR             R1, [ R7, I2C_CR1_PE_N * 4]       @ модуль I2C включается посредством установки бита PE в регистре CR1
exit_init_i2c:
                    POP             { R0 - R12, PC }



@.desc name=wait_not_busy type=proc
@ ****************************************************************************
@ *     Процедура ожидания освобождения линии I2C                            *
@ *     В R0 возвращает число проверок флага I2C_SR2_BUSY_N                  *
@ *         1 - линия была не занята                                         *
@ *         от 2 до MAX_CICLE_DELAY-1 включительно - линия освободилась      *
@ *         MAX_CICLE_DELAY - линия I2C занята                               *
@ ****************************************************************************
@ * Значимые входные данные:                                                 *
@ *  R10 = PERIPH_BB_BASE + ( I2Cx + I2C_SR2 ) * 32                          *
@ ****************************************************************************
@.enddesc
.global wait_not_busy
wait_not_busy:
                    PUSH            { R8, R9, LR }
                    MOV             R0, 0
                    MOV             R9, MAX_CICLE_DELAY
read_busy:
                    ADD             R0, R0, 1
                    CMP             R0, R9
                    BEQ             exit_wnb
                    LDR             R8, [ R10, I2C_SR2_BUSY_N * 4 ]
                    CMP             R8, 1
                    BEQ             read_busy
exit_wnb:
                    POP             { R8, R9, PC }


@.desc name=wait_TXE type=proc
@ ****************************************************************************
@ *     Процедура ожидания окончания передачи байта                          *
@ *                                                                          *
@ ****************************************************************************
@ * Значимые входные данные:                                                 *
@ *  R4 = PERIPH_BB_BASE + ( I2Cx + I2C_SR1 ) * 32                           *
@ ****************************************************************************
@.enddesc
.global wait_TXE
wait_TXE:       @  while (!(I2C1->SR1 & I2C_SR1_TXE)){} //Data register empty (transmitters)
                    PUSH            { R7, R9, LR }
                    MOV             R0, 0
                    MOV             R9, MAX_CICLE_DELAY
read_TXE:
                    ADD             R0, R0, 1
                    CMP             R0, R9
                    BEQ             exit_wt
                    @     добавить проверку флага AF
                    @     if (I2C_IsAcknowledgeFailed(hi2c) != HAL_OK) return HAL_ERROR;
                    LDR             R7, [ R4, I2C_SR1_TXE_N * 4 ]
                    CMP             R7, 0
                    BEQ             read_TXE
exit_wt:
                    POP             { R7, R9, PC }

@.desc name=wait_EV6 type=proc
@ ****************************************************************************
@ *     Процедура ожидания события EV6                                       *
@ ****************************************************************************
@ * Значимые входные данные:                                                 *
@ *  R0 = 0/1 (TX/RX)                                                        *
@ *  R5 = I2Cx + I2C_SR2                                                     *
@ *  R6 = I2Cx + I2C_SR1                                                     *
@ ****************************************************************************
@.enddesc
.global wait_EV6
wait_EV6:
                    PUSH            { R5, R6, R7, R8, R9, LR }
                    CMP             R0, TX
                    ITE             EQ
                    LDREQ           R7, = I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED
                    LDRNE           R7, = I2C_EVENT_MASTER_RECEIVER_MODE_SELECTED

                    MOV             R0, 0
                    MOV             R9, MAX_CICLE_DELAY
wait_SR1SR2:
                    ADD             R0, R0, 1
                    CMP             R0, R9
                    BEQ             exit_wev6

                    MOV             R8, 0
                    LDRH            R8, [ R6 ]    @ ожидаем окончания передачи адреса cобытие EV6
                    AND             R8, R8, R7
                    CMP             R8, R7
                    BNE             wait_SR1SR2
exit_wev6:
@wait_addr:                                        @     while (!(I2C1->SR1 & I2C_SR1_ADDR)){}
@                    LDR             R7, [ R4, I2C_SR1_ADDR_N * 4 ]
@                    CMP             R7, 0    @ 0
@                    BEQ             wait_addr

                    LDRH            R7, [ R6 ]    @     (void) I2C1->SR1;  сбрасываем бит ADDR (адрес принят) (чтением SR1 и SR2):
                    LDRH            R7, [ R5 ]    @     (void) I2C1->SR2;

                    POP             { R5, R6, R7, R8, R9, PC }


@.desc name=wait_SB type=proc
@ ****************************************************************************
@ *     Процедура ожидания формирования сигнала "Старт"                      *
@ *                                                                          *
@ ****************************************************************************
@ * Значимые входные данные:                                                 *
@ *  R4 = PERIPH_BB_BASE + ( I2Cx + I2C_SR1 ) * 32                           *
@ *  R6 = I2Cx + I2C_SR1                                                     *
@ ****************************************************************************
@.enddesc
.global wait_SB
wait_SB:                                            @     while (!(I2C1->SR1 & I2C_SR1_SB)){}         // ждем пока формируется  "Старт"
                    PUSH            { R7, R9, LR }
                    MOV             R0, 0
                    MOV             R9, MAX_CICLE_DELAY
read_SB:
                    ADD             R0, R0, 1
                    CMP             R0, R9
                    BEQ             exit_ws
                    LDR             R7, [ R4, I2C_SR1_SB_N * 4 ]
                    CMP             R7, 0
                    BEQ             read_SB
                    LDRH            R7, [ R6 ]         @ (void) I2C1->SR1;// flag = I2C1->SR1;//читаем SR1, для сброса бита SB.
exit_ws:
                    POP             { R7, R9, PC }

@.desc name=wait_BTF type=proc
@ ****************************************************************************
@ *     Процедура ожидания завершения отправки байта                         *
@ *                                                                          *
@ ****************************************************************************
@ * Значимые входные данные:                                                 *
@ *  R4 = PERIPH_BB_BASE + ( I2Cx + I2C_SR1 ) * 32                           *
@ ****************************************************************************
@.enddesc
.global wait_BTF
wait_BTF:
                    PUSH            { R7, R9, LR }
                    MOV             R0, 0
                    MOV             R9, MAX_CICLE_DELAY
read_BTF:
                    ADD             R0, R0, 1
                    CMP             R0, R9
                    BEQ             exit_wbtf
                    @    if (__HAL_I2C_GET_FLAG(hi2c, I2C_FLAG_AF) == SET)      /* Check if a NACK is detected */
                    @    {
                    @         __HAL_I2C_CLEAR_FLAG(hi2c, I2C_FLAG_AF);
                    @         return HAL_ERROR;
                    @    }
                    LDR             R7, [ R4, I2C_SR1_BTF_N * 4 ]
                    CMP             R7, 0
                    BEQ             read_BTF
exit_wbtf:
                    POP             { R7, R9, PC }


@.desc name=wait_RXNE type=proc
@ ****************************************************************************
@ *     Процедура ожидания появления нового байта на приёме                  *
@ *                                                                          *
@ ****************************************************************************
@ * Значимые входные данные:                                                 *
@ *  R4 = PERIPH_BB_BASE + ( I2Cx + I2C_SR1 ) * 32                           *
@ ****************************************************************************
@.enddesc
.global wait_RXNE
wait_RXNE:          @     while (!(I2C1->SR1 & I2C_SR1_RXNE)){}
                    PUSH            { R8, R9, LR }
                    MOV             R0, 0
                    MOV             R9, MAX_CICLE_DELAY
read_RXNE:
                    ADD             R0, R0, 1
                    CMP             R0, R9
                    BEQ             exit_wr
                    LDR             R8, [ R4, I2C_SR1_RXNE_N * 4 ]
                    CMP             R8, 0
                    BEQ             read_RXNE
exit_wr:
                    POP             { R8, R9, PC }


@.desc name=I2C_Mem_Write type=proc
@ ****************************************************************************
@ *  Входные данные:                                                         *
@ *  R11 - Адрес в памяти с данными для отправки                             *
@ *  R7 - Количество байт для отправки                                       *
@ *  R8 - DevAddress                                                         *
@ *  R12 - MemAddress                                                        *
@ *  R2 = I2Cx + I2C_DR                                                      *
@ *  R3 = PERIPH_BB_BASE + ( I2Cx + I2C_CR1 ) * 32                           *
@ *  R4 = PERIPH_BB_BASE + ( I2Cx + I2C_SR1 ) * 32                           *
@ *  R5 = I2Cx + I2C_SR2                                                     *
@ *  R6 = I2Cx + I2C_SR1                                                     *
@ *  R10 = PERIPH_BB_BASE + ( I2Cx + I2C_SR2 ) * 32                          *
@ ****************************************************************************
@.enddesc
.global I2C_Mem_Write
I2C_Mem_Write:
                    PUSH            { R2 - R12, LR }

                    BL              wait_not_busy
                    MOV             R9, MAX_CICLE_DELAY
                    CMP             R0, R9
                    BEQ             exit_error_i2c_busy_sc
                         @    /* Check if the I2C is already enabled */
                         @    if ((hi2c->Instance->CR1 & I2C_CR1_PE) != I2C_CR1_PE) __HAL_I2C_ENABLE(hi2c); /* Enable I2C peripheral */
                    MOV             R1, 0                   @ CLEAR_BIT(hi2c->Instance->CR1, I2C_CR1_POS);            /* Disable Pos */
                    STR             R1, [ R3, I2C_CR1_POS_N * 4 ]

                    MOV             R1, 1                   @ I2C1->CR1 |= I2C_CR1_START;// формирование сигнала старт
                    STR             R1, [ R3, I2C_CR1_START_N * 4 ]

                    BL              wait_SB
                    MOV             R9, MAX_CICLE_DELAY
                    CMP             R0, R9
                    BEQ             exit_start_error_sc
                                                            @ Send Slave Address and Memory Address
                    STRB            R8, [ R2 ]              @ передаем DevAddress @  I2C1->DR
                    BL              wait_EV6
                    STRB            R12, [ R2 ]             @ передаем MemAddress  @  I2C1->DR
send_bytes:
                    BL              wait_TXE
                    LDRB            R1, [ R11 ], 1
                    STRB            R1, [ R2 ]              @  I2C1->DR

                    SUBS            R7, R7, 1
                    BNE             send_bytes

                    BL              wait_BTF                @ (BTF: Byte transfer finished)

                    MOV             R1, 1                   @ I2C1->CR1 |= I2C_CR1_STOP ;// формирование сигнала "Стоп"
                    STR             R1, [ R3, I2C_CR1_STOP_N * 4 ]

                    B               exit_sc
exit_start_error_sc:
                    MOV             R0, I2C_START_ERROR
                    B               exit_error_sc
exit_error_i2c_busy_sc:
                    MOV             R0, I2C_BUSY            @  else  return HAL_BUSY;
exit_error_sc:
                    MOV             R1, 0

exit_sc:            POP             { R2 - R12, PC }



