Pull to refresh

Запуск I2S трансивера на Artery

Level of difficultyEasy
Reading time17 min
Views4.1K

В этом тексте я написал про то как самому написать System Software уровня HAL для ARM Cortex-M4 совместимого микроконтроллера.

Пролог

Некоторые компании сами пишут своё system software базового уровня. Как его только не называют: MAL, HAL, EHAL, SPL, драйвера, библиотеки. А на западе это называют просто MCAL. Пишут сами даже вопреки тому, что этот код бесплатно и в открытом виде дают производители всех микроконтроллеров. Такой эрзац нужен из-за недоверия к стороннему коду.

Поэтому программисты-микроконтроллеров годами пишут эти драйверы самого низкого уровня для того чтобы пользоваться всеми подсистемами вмонтированными внутрь микроконтроллеров: CLOCK, INTERRUPTS, GPIO, FLASH, RTC, UART, PWM, TIMER, WATCDOG, ADC, SDIO, USB, SWD, PDM, DAC, CAN, I2C, SPI, I2S, DMA.

Словом, для подготовки полного комплекта MCAL для очередного семейства MCU работы нужно проделать море...

По сути это код-переходник, программный-клей между удобной красивой функцией вида

bool i2s_write(uint8_t num, uint8_t* const data, size_t size);

и чтением и записью сырых физических регистров, что живут в карте памяти данного микроконтроллера. Ниже кода чем HAL просто не бывает... Разве, что Verilog. В HAL всё сводится к банальному чтению и записи нужных битов внутри регистров и прокручиванию какого-нибудь простенького конечного автомата. Установил bit(тик) и внутри MCU начала бушевать и дрязгать какая-н цифровая цепь.

Вот и настало время теперь так же голыми руками запустить и аппаратный I2S трансивер на чипке AT32F437 от Artery Technology.

Теория

I2S - это синхронный, последовательный 4хпроводной физический полнодуплексный интерфейс для передачи цифрового звука в пределах одной PCB. Вот шпаргалка по I2S

Cheat sheet по I2S
Cheat sheet по I2S

binding(и) - это функции, которые просто вызывают другие функции. В переводе на кухонный язык - программный клей. Binding(и) нужны, чтобы связать два совершенно разных API. Вот пример функции binding(а)

bool i2s_write(uint8_t num, uint16_t* const array, uint16_t words) {
    bool res = false;
    LOG_DEBUG(I2S, "Write,Wait,i2s:%u,Words:%u", num, words);
    I2sHandle_t* Node = I2sGetNode(num);
    if(Node) {
        Node->tx_done = false ;
        HAL_StatusTypeDef ret = 0;
        ret = HAL_I2S_Transmit_DMA(&Node->i2s_h, 
                                   array, 
                                   words);
        if(HAL_OK == ret) {
            res = true;
        } else {
            LOG_ERROR(I2S, "WrErr:%u=%s", ret, HalStatus2Str(ret));
        }
    } else {
        LOG_ERROR(I2S, "I2S%u,NodeErr", num);
    }
    return res;
}

Каков план?

Я сейчас не буду писать binding(и) для вызова Artery HAL кода функциями с другими именами. Я буду сам писать внутренности, свой вариант HAL для I2S.

Чтобы запустить I2S на любом микроконтроллере надо выполнить вот эти действия.

1--Определить и выбрать GPIO пины, которые аппаратно поддерживают I2S2. В Artery (как и у STM) это далеко не каждый пин на корпусе микросхемы.

2--Подключить тактирование на I2S трансивер.

3--Настроить пред делители тактовой частоты, чтобы выставить целевую частоту дискретизации для звуковых семплов.

4--Активировать прерывания I2S контроллера в ARM Cortex M4 процессоре

5--Прописать корректные значения в регистры I2S интерфейса.

6--Активировать прерывание окончания отправки, прерывание окончания приема, прерывание ошибок в карте регистров трансивера от Artery

7--Определить Си функцию обработчик прерывания для I2S2.

Обо всем этом обычно можно даже не догадываться, когда пишешь прошивку на основе HAL от производителя, однако это в самом деле надо проделывать. Иначе ничего не заработает.

Что надо из оборудования?

Для запуска аудио подсистемы надо достаточно много оборудования:

Оборудование

Количество

1

Логический анализатор

1

9

Осциллограф

1

11

Соединители WAGO (3pin)

3

10

щупы для осциллографа

4

2

учебно-треннировочная электронная плата AT-Start-F437

1

3

Кабель USB-A-USB-micro

1

4

DMM

1

5

Отладочная плата с аудиокодеком, например WM8731

1

6

перемычки гнездо-гнездо

10+

7

наушники с audio jack 3.5

1

8

микрофон с audio jack 3.5

1

Фаза 1: Подать тактирование на I2S2 подсистему.

В цифровой электронике всё надо тактировать. Без тактирования ничего не будет работать. Даже регистры I2S не сможете прописать, если нет тактирования на I2S контроллере. Активация тактирования происходит в регистре APB1EN в подсистеме тактирования CRM. Буквально одним битом. Также надо подать тактирование на нужный GPIO.

page 45
page 45

Фаза 2: Переключить GPIO пины на альтернативную функцию.

Перед тем как приступить к I2S вы должны досконально понимать GPIO периферию. Что значит каждый бит в карте памяти GPIO. В частности в регистре GPIOx_CFGR происходит назначение первого мультиплексора PINMUX на альтернативную функцию. Надо переключить 5 пинов на I2S2.

Фаза 3: Определить GPIO пины на I2S2.

При этом самих альтернативных функций для каждого GPIO пина может быть десятки. Поэтому надо переключить второй PINMUX мультиплексор GPIO на конкретную альтернативную функцию. В данном случае это I2S2. И так для каждого провода в шине I2S.

Затем надо в UART CLI убедиться, что Mux регистр в самом деле прописался.

Фаза 4: Определить регистры I2S периферии в нужные значения.

В программировании MCU достаточно только правильно выставить регистры и электрическая схема I2S внутри микросхемы заработает сама собой. Настроек у I2S много, но вот основные

параметр

количество бит

1

роль на шине

2

2

разрядность семпла

2

3

предделители частоты

10

4

прерывание на прием

1

5

прерывание на отправку

1

6

полярность защелкивания бита

1

7

прерывание на ошибку

1

8

вывод частоты тактирования за корпус

1

Тут самое сложное и не очевидное - это правильно настроить частоту дискретизации звуковых отсчетов Fs. Чтобы просто установить желаемой частоту надо правильно прописать аж 4 отдельных битовых поля в разных регистрах: I2SMCLKOE, I2SCBN, I2SDIV, I2SODD. Тут видно ещё и статические предделители 4 и 8. Надо их тоже учитывать в коде прошивки.

Вот так выглядит сырая карта регистров. Всего 9 регистров по 32 бита каждый. Чтобы правильно заработал I2S надо правильно прописать всего-навсего 288 бит

Сырые значения регистров I2S
Сырые значения регистров I2S

Вот так это выглядит детализация битовых полей в карте регистров I2S

разбор битовых полей регистров I2S
разбор битовых полей регистров I2S

Фаза 5: Определить обработчик прерывания для I2S2

В I2S прерывания происходят после отправки каждого канала. То есть с частотой 2xFs. Или каждые 32 бита. Если вы испускаете звук с частотой 48kHz, то прерывания будут происходить с частотой 96kHz.

В нашем случае обработчик прерываний - это функция SPI2_I2S2EXT_IRQHandler


static bool I2sIRQHandler(uint8_t num) {
    bool res = false;
    I2sHandle_t* Node = I2sGetNode(num);
    if(Node) {
        Node->it_cnt++;
        Node->it_done = true;
        gpio_toggle(Node->PadDebug1.byte);

        res = i2s_interrupt_flag_get_ll(Node->I2Sx, I2S_FLAG_RDBF);
        if(res) {
            Node->rx_buff_full = true;
            if(Node->rec) {
                Node->Rx.cnt++;
                bool overflow = false;
                Node->Rx.index = inc_index(Node->Rx.index, Node->Rx.size, &overflow);
                Node->Rx.overflow += overflow;
                uint16_t word = i2s_data_receive_ll(Node->I2Sx);
                Node->Rx.array[Node->Rx.index] = (SampleType_t)word;
                i2s_interrupt_ctrl_ll(Node->I2Sx, I2S_INTERRUPT_RX_FULL, true);
                if(overflow) {
                    if(I2S_STATE_IDLE == Node->state) {
                        Node->rec = false;
                    }
                }
            } else {
                i2s_interrupt_ctrl_ll(Node->I2Sx, I2S_INTERRUPT_RX_FULL, false);
            }
        }

        res = i2s_interrupt_flag_get_ll(Node->I2Sx, I2S_FLAG_TDBE);
        if(res) {
            res = i2s_interrupt_ctrl_ll(Node->I2Sx, I2S_INTERRUPT_TX_EMPTY, true);
            Node->tx_buff_empty = true; // 555
            Node->tx_buff_empty_cnt++;
            Node->Tx.cnt++;
            bool overflow = false;
            Node->Tx.index = inc_index(Node->Tx.index, Node->Tx.size, &overflow);
            Node->Tx.overflow += overflow;
            if(Node->Tx.array) {
                if(Node->play) {
                    i2s_data_transmit_ll(Node->I2Sx, Node->Tx.array[Node->Tx.index]);
                } else {
                    i2s_interrupt_ctrl_ll(Node->I2Sx, I2S_INTERRUPT_TX_EMPTY, false);
                }
            } else {
                i2s_interrupt_ctrl_ll(Node->I2Sx, I2S_INTERRUPT_TX_EMPTY, false);
            }
        }
    }
  ......
    return res;
}

void SPI2_I2S2EXT_IRQHandler(void) { 
  I2sIRQHandler(2); 
}

В массиве векторов прерываний у ARM Cortex-M4 в исполнении AT32F437 прерывание для I2S2 имеет индекс 36. Также необходимо, чтобы в массиве векторов прерываний был прописан flash адрес на функцию обработчик прерывания для I2S2 (SPI2_I2S2EXT_IRQHandler).

Фаза 6: Включить прерывания I2S на уровне ядра

Чтобы прерывания по I2S вообще происходили их надо включить на уровне микропроцессорного ядра ARM-Cortex-M4. Для этого надо в регистрах котроллера NVIC активировать нужные биты.

/**
  \brief   Enable Interrupt
  \details Enables a device specific interrupt in the NVIC interrupt controller.
  \param [in]      IRQn  Device specific interrupt number.
  \note    IRQn must not be negative.
 */
void NVIC_EnableIRQ(IRQn_Type IRQn)

В моем случае это IRQn c номерами 51(I2S3) и 36(I2S2).

Код ядра драйвера I2S

Вот код ядра драйвера I2S
#include "i2s_mcal.h"

#include "clock.h"
#include "code_generator.h"
#include "gpio_mcal.h"
#include "i2s_custom_types.h"
#include "i2s_register_types.h"
#include "log.h"
#include "mcal_types.h"
#include "sys_config.h"

static I2sBitLen_t I2sDataFormatToArtery(I2sDataFormat_t data_format) {
    I2sBitLen_t data_length = I2SDBN_UNDEF;
    switch(data_format) {
    case I2S_DATA_FORMAT_8B:
        data_length = I2SDBN_UNDEF;
        break;
    case I2S_DATA_FORMAT_16B:
        data_length = I2SDBN_16BIT;
        break;
    case I2S_DATA_FORMAT_16B_EXTENDED:
        data_length = I2SDBN_16BIT;
        break;
    case I2S_DATA_FORMAT_24B:
        data_length = I2SDBN_24BIT;
        break;
    case I2S_DATA_FORMAT_32B:
        data_length = I2SDBN_32BIT;
        break;
    default:
        data_length = OPERSEL_UNDEF;
        break;
    }
    return data_length;
}

static I2sDataFormat_t I2sArteryToDataFormat(I2sBitLen_t data_length) {
    I2sDataFormat_t data_format = I2S_DATA_FORMAT_UNDEF;
    switch(data_length) {
    case I2SDBN_16BIT:
        data_format = I2S_DATA_FORMAT_16B;
        break;
    case I2SDBN_24BIT:
        data_format = I2S_DATA_FORMAT_24B;
        break;
    case I2SDBN_32BIT:
        data_format = I2S_DATA_FORMAT_32B;
        break;
    default:
        data_format = I2S_DATA_FORMAT_UNDEF;
        break;
    }
    return data_format;
}

static I2sOperation_t I2sRoleToArtery(I2sRole_t bus_role) {
    I2sOperation_t operation = OPERSEL_UNDEF;
    switch(bus_role) {
    case I2SMODE_SLAVE:
        operation = OPERSEL_SLAVE_RX;
        break;
    case I2SMODE_SLAVE_TX:
        operation = OPERSEL_SLAVE_TX;
        break;
    case I2SMODE_SLAVE_RX:
        operation = OPERSEL_SLAVE_RX;
        break;
    case I2SMODE_MASTER:
        operation = OPERSEL_MASTER_TX;
        break;
    case I2SMODE_MASTER_TX:
        operation = OPERSEL_MASTER_TX;
        break;
    case I2SMODE_MASTER_RX:
        operation = OPERSEL_MASTER_RX;
        break;
    default:
        operation = OPERSEL_MASTER_TX;
        break;
    }
    return operation;
}

static I2sRole_t I2sArteryToRole(I2sOperation_t operation) {
    I2sRole_t role = I2SMODE_UNDEF;
    switch(operation) {
    case OPERSEL_SLAVE_RX:
        role = I2SMODE_SLAVE_RX;
        break;
    case OPERSEL_SLAVE_TX:
        role = I2SMODE_SLAVE_TX;
        break;
    case OPERSEL_MASTER_TX:
        role = I2SMODE_MASTER_TX;
        break;
    case OPERSEL_MASTER_RX:
        role = I2SMODE_MASTER_RX;
        break;
    default:
        role = I2SMODE_UNDEF;
        break;
    }
    return role;
}

bool i2s_div_get(uint8_t num, uint16_t* division) {
    bool res = false;
    I2sDiv_t Div;
    Div.division = 0;
    const I2sInfo_t* Info = I2sGetInfo(num);
    if(Info) {
        if(division) {
            Div.division_7_0 = Info->I2Sx->SPI_I2SCLKP.I2SDIV1;
            Div.division_11_10 = Info->I2Sx->SPI_I2SCLKP.I2SDIV2;
            LOG_DEBUG(I2S, "I2S:%u,Div:%u", num, Div.division);
            *division = Div.division;
            res = true;
        }
    }
    return res;
}

static I2sStandart_t I2sStandardToArtery(Standard_t standard) {
    I2sStandart_t artery_std = I2SCLKPOL_UNDEF;
    switch(standard) {
    case I2S_STD_PHILIPS:
        artery_std = STDSEL_PHILIPS;
        break;
    case I2S_STD_MSB:
        artery_std = STDSEL_RIGHT_ALIGNED;
        break;
    case I2S_STD_LSB:
        artery_std = STDSEL_LEFT_ALIGNE;
        break;
    case I2S_STD_PCM_SHORT:
        artery_std = STDSEL_PCM;
        break;
    case I2S_STD_PCM_LONG:
        artery_std = STDSEL_PCM;
        break;
    default:
        break;
    }
    return artery_std;
}

const Reg32_t I2sReg[] = {
    {
        .valid = true,
        .name = "SPI_CTRL1",
        .offset = 0x00,
    },
    {
        .valid = true,
        .name = "SPI_CTRL2",
        .offset = 0x04,
    },
    {
        .valid = true,
        .name = "SPI_STS",
        .offset = 0x08,
    },
    {
        .valid = true,
        .name = "SPI_DT",
        .offset = 0x0C,
    },
    {
        .valid = true,
        .name = "SPI_CPOLY",
        .offset = 0x10,
    },
    {
        .valid = true,
        .name = "SPI_RCRC",
        .offset = 0x14,
    },
    {
        .valid = true,
        .name = "SPI_TCRC",
        .offset = 0x18,
    },
    {
        .valid = true,
        .name = "SPI_I2SCTRL",
        .offset = 0x1C,
    },
    {
        .valid = true,
        .name = "SPI_I2SCLKP",
        .offset = 0x20,
    },

};

const static I2sInfo_t I2sInfo[] = {
#ifdef HAS_I2S1
    {
        .num = 1,
        .valid = true,
        .I2Sx = SPI1,
        .clock_bus = BUS_APB2,
        .irq_n = SPI1_IRQn,
        .clock_type = CLOCK_PERIPH_CLOCK_I2S1,
    },
#endif

#ifdef HAS_I2S2
    {
        .num = 2,
        .valid = true,
        .I2Sx = (I2sRegMap_t*)0x40003800,
        .clock_bus = BUS_APB1,
        .irq_n = SPI2_I2S2EXT_IRQn,
        .clock_type = CLOCK_PERIPH_CLOCK_I2S2,
    },
#endif

#ifdef HAS_I2S3
    {
        .num = 3,
        .valid = true,
        .I2Sx = I2S3EXT,
        .clock_bus = BUS_APB1,
        .irq_n = SPI3_I2S3EXT_IRQn,
        .clock_type = CLOCK_PERIPH_CLOCK_I2S3,
    },
#endif

#ifdef HAS_I2S4
    {
        .num = 4,
        .valid = true,
        .I2Sx = SPI4,
        .clock_bus = BUS_APB2,
        .irq_n = SPI4_IRQn,
        .clock_type = CLOCK_PERIPH_CLOCK_I2S4,
    },
#endif

};

COMPONENT_GET_INFO(I2s)

uint32_t i2s_reg_cnt(void) {
    uint32_t cnt = ARRAY_SIZE(I2sReg);
    return cnt;
}

/*The prescaler of the CK depends on whether to provide the main clock for peripherals. To ensure that
the main clock is always 256 times larger than the audio sampling frequency, the channel bits should be
taken into account. When the main clock is needed, the CK should be divided by 8 (I2SCBN=0) or 4
(I2SCBN=1), then divided again by the same prescaler as that of the MCK, that is the final
communication clock; When the main clock is not needed, the prescaler of the CK is determined by
I2SDIV and I2SODD, shown in Figure 13-13.*/
static uint32_t I2sChannelBitToDivider(I2sChannelBitNum_t i2scbn) {
    uint32_t cbn_divider = 1;
    switch((uint32_t)i2scbn) {
    case I2SCBN_16BIT_WIDE: {
        cbn_divider = 8;
    } break;
    case I2SCBN_32BIT_WIDE: {
        cbn_divider = 4;
    } break;
    default:
        break;
    }
    return cbn_divider;
}

bool i2s_ctrl_ll(I2sRegMap_t* const I2Sx, bool on) {
    bool res = false;
    if(I2Sx) {
        I2Sx->SPI_I2SCTRL.I2SEN = on;
        I2Sx->SPI_I2SCTRL.I2SMSEL = I2SMSEL_I2S;
        res = true;
    }
    return res;
}

bool i2s_ctrl_l(I2sHandle_t* const Node, bool on) {
    bool res = false;
    if(Node) {
        res = i2s_ctrl_ll(Node->I2Sx, on);
    }
    return res;
}

bool i2s_ctrl(uint8_t num, bool on_off) {
    bool res = false;
    I2sHandle_t* Node = I2sGetNode(num);
    if(Node) {
        Node->I2Sx->SPI_I2SCTRL.I2SEN = on_off;
        Node->I2Sx->SPI_I2SCTRL.I2SMSEL = I2SMSEL_I2S;
        res = true;
    }
    return res;
}

bool i2s_dma_stop(uint8_t num) {
    bool res = false;
    I2sHandle_t* Node = I2sGetNode(num);
    if(Node) {
        Node->state = I2S_STATE_OFF;
        res = i2s_ctrl_l(Node, false);
        LOG_INFO(I2S, "Stop");
    }
    return res;
}

static I2sClockPolatity_t I2sClockPolarityToArtery(Cpol_t cpoll) {
    I2sClockPolatity_t clock_polar = I2SCLKPOL_UNDEF;
    switch(cpoll) {
    case I2S_CLOCK_POL_LOW:
        clock_polar = I2SCLKPOL_LOW;
        break;
    case I2S_CLOCK_POL_HIGH:
        clock_polar = I2SCLKPOL_HIGH;
        break;
    default:
        break;
    }
    return clock_polar;
}

bool i2s_data_transmit_ll(I2sRegMap_t* const I2Sx, uint16_t tx_data) {
    bool res = false;
    if(I2Sx) {
        I2Sx->SPI_DT.DT = tx_data;
        res = true;
    }
    return res;
}

uint16_t i2s_data_receive_ll(I2sRegMap_t* const I2Sx) {
    uint16_t word = 0;
    if(I2Sx) {
        word = (uint16_t)I2Sx->SPI_DT.DT;
    }
    return word;
}

bool i2s_standard_set(uint8_t num, Standard_t standard) {
    bool res = false;
    I2sHandle_t* Node = I2sGetNode(num);
    if(Node) {
        Node->I2Sx->SPI_I2SCTRL.STDSEL = I2sStandardToArtery(standard);
        res = true;
    }
    return res;
}

// see Figure 13-22 CK & MCK source in master mode
static uint32_t i2s_extra_divider_get_ll(I2sRegMap_t* const I2Sx) {
    uint32_t extra_div = 1;
    U32 i2smclkoe = I2Sx->SPI_I2SCLKP.I2SMCLKOE;
    if(i2smclkoe) {
        extra_div = I2sChannelBitToDivider(I2Sx->SPI_I2SCTRL.I2SCBN);
    } else {
        extra_div = 1;
    }
    return extra_div;
}

bool i2s_clock_polarity_set(uint8_t num, Cpol_t cpoll) {
    bool res = false;
    I2sHandle_t* Node = I2sGetNode(num);
    if(Node) {
        Node->I2Sx->SPI_I2SCTRL.I2SCLKPOL = I2sClockPolarityToArtery(cpoll);
        res = true;
    }
    return res;
}

static bool i2s_pinmux(uint8_t num, I2sRole_t bus_role) {
    bool res = false;
    LOG_INFO(I2S, "I2S_%u,Set,PinMux,BusRole:%s", num, I2sBusRole2Str(bus_role));
    const I2sConfig_t* Config = I2sGetConfig(num);
    if(Config) {
        res = true;
        switch((uint32_t)bus_role) {
        case I2SMODE_SLAVE: {
            res = false;
        } break;
        case I2SMODE_MASTER: {
            res = false;
        } break;

        case I2SMODE_SLAVE_RX:
        case I2SMODE_MASTER_RX: {
            res = gpio_init_one(&Config->GpioSdIn);
            res = gpio_deinit_one(Config->GpioSdOut.pad) && res;
        } break;

        case I2SMODE_SLAVE_TX:
        case I2SMODE_MASTER_TX: {
            res = gpio_init_one(&Config->GpioSdOut);
            res = gpio_deinit_one(Config->GpioSdIn.pad) && res;
        } break;
        default:
            res = false;
            break;
        }
    }
    return res;
}

bool i2s_bus_role_set(uint8_t num, I2sRole_t bus_role) {
    bool res = false;
    I2sHandle_t* Node = I2sGetNode(num);
    if(Node) {
        res = i2s_pinmux(num, bus_role);
        Node->I2Sx->SPI_I2SCTRL.OPERSEL = I2sRoleToArtery(bus_role);
        res = true;
    }
    return res;
}

bool i2s_bus_role_get(uint8_t num, I2sRole_t* const bus_role) {
    bool res = false;
    I2sHandle_t* Node = I2sGetNode(num);
    if(Node) {
        *bus_role = I2sArteryToRole(Node->I2Sx->SPI_I2SCTRL.OPERSEL);
        res = true;
    }
    return res;
}

static uint32_t I2sBitToDivider(I2sChannelBitNum_t i2s_cbn) {
    uint32_t bit_divider = 1;
    switch((uint32_t)i2s_cbn) {
    case I2SCBN_16BIT_WIDE:
        bit_divider = 16;
        break;
    case I2SCBN_32BIT_WIDE:
        bit_divider = 32;
        break;
    default:
        break;
    }
    return bit_divider;
}

static uint32_t I2sOddToDivider(I2sOddFactor_t i2s_odd) {
    uint32_t odd_divider = 1;
    switch((uint32_t)i2s_odd) {
    case I2SODD_EVEN:
        odd_divider = 2;
        break;
    case I2SODD_ODD:
        odd_divider = 3;
        break;
    default:
        break;
    }
    return odd_divider;
}

#define CHAN_CNT 2
// see 13.3.5 I2S_CLK controller
bool i2s_sample_freq_set(uint8_t num, AudioFreq_t audio_freq) {
    bool res = false;
    const I2sInfo_t* Info = I2sGetInfo(num);
    if(Info) {
        uint32_t base_freq_hz = clock_freq_get(Info->clock_bus);
        I2sHandle_t* Node = I2sGetNode(num);
        if(Node) {
            // see Figure 13-22 CK & MCK source in master mode
            uint32_t odd_div = I2sOddToDivider(Node->I2Sx->SPI_I2SCLKP.I2SODD);
            uint32_t extra_div = i2s_extra_divider_get_ll(Node->I2Sx);
            uint32_t bit_div = I2sBitToDivider(Node->I2Sx->SPI_I2SCTRL.I2SCBN);
            I2sDiv_t Div;
            uint32_t bit_ckock_hz = audio_freq * bit_div;
            Div.division = base_freq_hz / (bit_ckock_hz * CHAN_CNT * extra_div + odd_div);
            LOG_INFO(I2S, "BaseFreqHz:%u Hz,SampleFreq:%u Hz,Div:%u", base_freq_hz, audio_freq, Div.division);
            Node->I2Sx->SPI_I2SCLKP.I2SDIV1 = Div.division_7_0;
            Node->I2Sx->SPI_I2SCLKP.I2SDIV2 = Div.division_11_10;
            // Node->I2Sx->SPI_I2SCLKP.I2SODD = I2SODD_EVEN;
            res = true;
        }
    }

    return res;
}

// see 13.3.5 I2S_CLK controller
bool i2s_sample_freq_get(uint8_t num, uint32_t* const audio_freq) {
    bool res = false;
    const I2sInfo_t* Info = I2sGetInfo(num);
    if(Info) {
        uint32_t base_freq_hz = clock_freq_get(Info->clock_bus);

        I2sHandle_t* Node = I2sGetNode(num);
        if(Node) {
            uint16_t division = 0;
            res = i2s_div_get(num, &division);
            // see Figure 13-22 CK & MCK source in master mode
            uint32_t extra_div = i2s_extra_divider_get_ll(Node->I2Sx);
            uint32_t odd_div = I2sOddToDivider(Node->I2Sx->SPI_I2SCLKP.I2SODD);
            uint32_t bit_div = I2sBitToDivider(Node->I2Sx->SPI_I2SCTRL.I2SCBN);
            uint32_t clock_hz = 0;
            clock_hz = base_freq_hz / (2 * division * extra_div * bit_div + odd_div);
            LOG_PARN(I2S, "SCLK:%uHz,CK:%u Hz", base_freq_hz, clock_hz);
            *audio_freq = clock_hz;
            res = true;
        }
    }

    return res;
}

bool i2s_data_format_set(uint8_t num, I2sDataFormat_t data_format) {
    bool res = false;
    I2sHandle_t* Node = I2sGetNode(num);
    if(Node) {
        Node->I2Sx->SPI_I2SCTRL.I2SDBN = I2sDataFormatToArtery(data_format);
        Node->I2Sx->SPI_I2SCTRL.I2SCBN = I2SCBN_32BIT_WIDE;
        res = true;
    }

    return res;
}

bool i2s_config_tx(uint8_t num, I2sDataFormat_t word_size, uint8_t channels, AudioFreq_t audio_freq) {
    bool res = true;
    (void)channels;
    res = i2s_sample_freq_set(num, audio_freq) && res;
    res = i2s_data_format_set(num, word_size) && res;
    return res;
}

bool i2s_master_clock_ctrl(uint8_t num, MclkOut_t mclk_out) {
    bool res = false;
    I2sHandle_t* Node = I2sGetNode(num);
    if(Node) {
        Node->I2Sx->SPI_I2SCLKP.I2SMCLKOE = (I2sMasterClockOut_t)mclk_out;
        res = true;
    }
    return res;
}

bool i2s_data_format_get(uint8_t num, I2sDataFormat_t* data_format) {
    bool res = false;
    I2sHandle_t* Node = I2sGetNode(num);
    if(Node) {
        *data_format = I2sArteryToDataFormat(Node->I2Sx->SPI_I2SCTRL.I2SDBN);
        res = true;
    }

    return res;
}

uint8_t i2s_sample_size_get(uint8_t num) {
    uint8_t sample_size_bit = 0;
    I2sHandle_t* Node = I2sGetNode(num);
    if(Node) {
        switch(Node->I2Sx->SPI_I2SCTRL.I2SDBN) {
        case I2SDBN_16BIT:
            sample_size_bit = 16;
            break;
        case I2SDBN_24BIT:
            sample_size_bit = 26;
            break;
        case I2SDBN_32BIT:
            sample_size_bit = 32;
            break;
        default:
            sample_size_bit = 0;
            break;
        }
    }

    return sample_size_bit;
}

bool i2s_interrupt_ctrl_ll(I2sRegMap_t* const I2Sx, I2sInterrupt_t i2s_interrupt, bool on_off) {
    bool res = false;
    if(I2Sx) {
        switch(i2s_interrupt) {
        case I2S_INTERRUPT_ERROR: {
            I2Sx->SPI_CTRL2.ERRIE = on_off;
            res = true;
        } break;
        case I2S_INTERRUPT_RX_FULL: {
            I2Sx->SPI_CTRL2.RDBFIE = on_off;
            res = true;
        } break;
        case I2S_INTERRUPT_TX_EMPTY: {
            I2Sx->SPI_CTRL2.TDBEIE = on_off;
            res = true;
        } break;
        default: {
            res = false;
        } break;
        }
        res = true;
    }
    return res;
}

bool i2s_interrupt_ctrl_l(I2sHandle_t* Node, I2sInterrupt_t i2s_interrupt, bool on_off) {
    bool res = false;
    res = i2s_interrupt_ctrl_ll(Node->I2Sx, i2s_interrupt, on_off);
    return res;
}

bool i2s_interrupt_ctrl(uint8_t num, I2sInterrupt_t i2s_interrupt, bool on_off) {
    bool res = false;
    I2sHandle_t* Node = I2sGetNode(num);
    if(Node) {
        res = i2s_interrupt_ctrl_l(Node, i2s_interrupt, on_off);
    }
    return res;
}

static bool i2s_clock_ctrl(ClockBus_t clock_bus, uint32_t clock_type, bool on_off) {
    bool res = false;
    LOG_INFO(I2S, "ClkBus:%u,Mask:0x%x", clock_bus, clock_type);
    if(on_off) {
        switch((uint32_t)clock_bus) {
        case BUS_APB1: {
            CRM.APB1EN.R |= clock_type;
        } break;
        case BUS_APB2: {
            CRM.APB2EN.R |= clock_type;
        } break;
        }
    } else {
        switch((uint32_t)clock_bus) {
        case BUS_APB1: {
            CRM.APB1EN.R &= ~clock_type;
        } break;
        case BUS_APB2: {
            CRM.APB2EN.R &= ~clock_type;
        } break;
        }
    }
    return res;
}

/*
 * samples mast be even
 */
bool i2s_api_read(uint8_t num, SampleType_t* const array, size_t samples) {
    bool res = false;
    I2sHandle_t* Node = I2sGetNode(num);
    if(Node) {
        I2sRole_t bus_role = I2SMODE_UNDEF;
        res = i2s_bus_role_get(num, &bus_role);
        if(I2SMODE_MASTER_RX != bus_role) {
            res = i2s_bus_role_set(num, I2SMODE_MASTER_RX);
        }

        LOG_INFO(I2S, "I2S%u,Read:%u Sam", num, samples);
        Node->state = I2S_STATE_REC;
        Node->rec = true;
        Node->Rx.index = 0;
        Node->Rx.size = samples;
        Node->Rx.array = array;
        res = i2s_interrupt_ctrl_ll(Node->I2Sx, I2S_INTERRUPT_RX_FULL, true);
        res = i2s_interrupt_ctrl_ll(Node->I2Sx, I2S_INTERRUPT_TX_EMPTY, false);
        i2s_data_receive_ll(Node->I2Sx);
        res = i2s_ctrl_ll(Node->I2Sx, true);
        res = true;
    }
    return res;
}

bool i2s_api_write(uint8_t num, SampleType_t* const array, size_t size) {
    bool res = false;
    LOG_INFO(I2S, "Write,I2S_%u,Words:%u", num, size);
    I2sHandle_t* Node = I2sGetNode(num);
    if(Node) {
        I2sRole_t bus_role = I2SMODE_UNDEF;
        res = i2s_bus_role_get(num, &bus_role);
        if(I2SMODE_MASTER_TX != bus_role) {
            res = i2s_bus_role_set(num, I2SMODE_MASTER_TX);
        }

        Node->state = I2S_STATE_RUN;
        Node->Tx.array = array;
        Node->Tx.size = size * 2; /*1 sample 2 channel*/
        Node->Tx.index = 0;
        Node->play = true;
        res = i2s_interrupt_ctrl_ll(Node->I2Sx, I2S_INTERRUPT_RX_FULL, false);
        res = i2s_interrupt_ctrl_ll(Node->I2Sx, I2S_INTERRUPT_TX_EMPTY, true);
        res = i2s_data_transmit_ll(Node->I2Sx, array[0]);
        res = i2s_ctrl_ll(Node->I2Sx, true);
        res = true;
    }
    return res;
}

bool i2s_init_one(uint8_t num) {
    bool res = false;
    const I2sConfig_t* Config = I2sGetConfig(num);
    if(Config) {
        LOG_WARNING(I2S, "%s", I2sConfigToStr(Config));
        const I2sInfo_t* Info = I2sGetInfo(num);
        if(Info) {
            I2sHandle_t* Node = I2sGetNode(num);
            if(Node) {
                res = i2s_clock_ctrl(Info->clock_bus, Info->clock_type, true);

                Node->I2Sx = Info->I2Sx;
                res = i2s_init_common(Config, Node);
                uint32_t base_freq_hz = clock_freq_get(Info->clock_bus);
                LOG_INFO(I2S, "BaseFreqHz:%u Hz", base_freq_hz);

                res = i2s_master_clock_ctrl(num, Config->mclk_out);
                res = i2s_standard_set(num, Config->standard);
                res = i2s_clock_polarity_set(num, Config->cpol);
                res = i2s_bus_role_set(num, Config->bus_role);
                res = i2s_data_format_set(num, Config->data_format);
                res = i2s_sample_freq_set(num, Config->audio_freq);

                if(Config->interrupt_on) {
                    res = i2s_interrupt_ctrl(num, I2S_INTERRUPT_ERROR, true);
                    res = i2s_interrupt_ctrl(num, I2S_INTERRUPT_RX_FULL, true);
                    res = i2s_interrupt_ctrl(num, I2S_INTERRUPT_TX_EMPTY, true);
                    NVIC_SetPriority(Info->irq_n, Config->irq_priority);
                    NVIC_EnableIRQ(Info->irq_n);
                }
                res = i2s_ctrl_ll(Node->I2Sx, false);
            }
        }
    }
    return res;
}

bool i2s_init_custom(void) {
    bool res = false;
    return res;
}

Отладка

Для проверки I2S я собрал вот такой прототип. Это двух ярусная сборка. Основа - это учебно-треннировочная электронная плата AT-Start-F437. Сверху примонтирована электронная плата с аудио кодеком WM8731.

Вот так выглядит вся аппаратура для отладки I2S в натуре

Лог начальной загрузки показал, что I2S2 успешно стартовал

Теперь остается только тут же в UART-CLI консоли попросить прошивку включить тестовый синус сигнал.

Пристегнув логический анализатор I2S я увидел, что данные в самом деле отправляются.

Звук было слышно в наушниках даже не надевая их.

Аналогично с записью. Я просто включаю на смартфоне тон 2500Hz, набираю в UART-CLI команду записи 5.3ms звука, вычисляю в прошивке DFT и смотрю пиковую частоту.

Получилось как раз 2437 Hz. Ошибка меньше 1 %. Значит запись звука тоже работает волшебно. Успех!

Примечания

1--На самом деле вам даже не обязательно обладать аппаратным аудиокодеком WM8731 для того чтобы проверить I2S. Внутри MCU AT32F437MT заложено четыре I2S трансивера. В связи с этим, можно просто тремя проводками соединить пины I2S3 и I2S2 трансиверов, включить трансляцию данных, подождать, скажем , 300ms, отключить трансляцию и сверить переданные и принятые данные. Если счетчик принятых семплов положительный и принятые данные совпадают с переданными, то можно смело утверждать, что драйвер I2S работает. Это называется режим LoopBack.

I2S в режиме LoopBack
I2S в режиме LoopBack

Вот дополненная реальность для правильного подключения обвязки

дополненная реальность
дополненная реальность

Вот так LoopBack тест выглядит в натуре

loopback test. Натурные испытания.
loopback test. Натурные испытания.

2--Стоит также отметить, что в Artery MCU каждый отдельный I2S может быть либо передатчиком, либо приёмником. Ника не одновременно.

Если же мы хотим сделать полнодуплексную трансляцию, то надо настроить синхронную работу двух отдельных I2S трансиверов (I2S2 и I2S2EXT).

Итоги

Удалось самому написать MCAL для I2S на Artery MCU семейства AT32F4xx. В этом оказывается нет ничего сложного. Просто механика. Главное внимательно читать спеку на микроконтроллер и уметь корректно интерпретировать карту регистров в I2S адресов.

При том в отладке System SW очень удружила UART-CLI для ручного вызова функций из API I2S драйвера и чтения состояния софта и железа. UART-Shell как лакмусовая бумажка показывает текущее состояние софта и железа.

В сухом остатке примерно так и инициализируются любая подсистема на любом микроконтроллере. Где-то чуть сложнее где-то чуть проще, но в целом это очень обыкновенная и упорная работа.

Акроним

Расшивровка

API

application programming interface

MCAL

Microcontroller Abstraction Layer

DFT

Discrete Fourier transform

GPIO

General-purpose input/output

UART

universal asynchronous receiver-transmitter

CLI

command-line interface

MCU

Microcontroller

ARM

Advanced RISC Machines

AT

Artery Technology

I2S

Inter-Integrated Circuit Sound

Ссылки

Only registered users can participate in poll. Log in, please.
Вы работали с интерфейсом I2S?
48.15% да13
51.85% нет14
27 users voted. 2 users abstained.
Only registered users can participate in poll. Log in, please.
Как вы решаете вопрос с выбором HAL для микроконтроллера?
25.81% Да, я сам писал\пишу HAL\MCAL код для микроконтроллеров.8
58.06% Нет. Я обычно беру официальный HAL от производителя микроконтроллера и использую его как есть18
16.13% Нет, я пишу только binding(и) к HAL от вендора и использую чужой код через binding(и)5
31 users voted. 4 users abstained.
Only registered users can participate in poll. Log in, please.
Вы программировали микроконтроллеры от Artery Technology?
22.58% да7
77.42% нет24
31 users voted. 3 users abstained.
Tags:
Hubs:
Total votes 10: ↑7 and ↓3+8
Comments16

Articles