STM32 Синтезатор речи “Элиза”. Продолжение

Проведя ряд испытаний понял, что 8-ми разрядный звук – это не самое лучшее решение. При маленькой громкости слышны искажения.

Экспериментально вычислил, что оптимальным будет использование файлов 16-разрядных с частотой дискретизации 22050 Гц. В итоге получится тот же поток данных, что и при 8-ми разрядах и частоте 44100.

Изменения в проигрыватель WAV файлов внёс. Вместо 16-ти разрядов он реально использует 12, но это значительно лучше, чем 8. С частотой 22050 Гц он достойно справляется, а вот 44100 уже не берет.

Если кого заинтересуют исходники, то приведу в порядок и выложу их.

Помимо изменений в программе сделал устройство в “железе”. До этого использовал отладочную плату.

Схема устройства следующая:

image_thumb7

Сразу предусмотрел УНЧ, чтобы можно было проверить устройство (а может и использовать) без внешнего усилителя. Данная микросхема способна выдать до 1Вт при нагрузке 8 Ом. Не знаю как она воспроизводит один ват, но пол вата точно выдает.

Из приятных бонусов следует отметить наличие в этой микросхеме регулировки усиления (читай громкости) напряжением. Для этого предназначен вывод 4. Я его подключил к выходу второго ЦАПа, благодаря чему контроллер может регулировать громкость.

После сборки устройство заработало сразу, но проявился один косяк. Помимо полезного сигнала прослушивается небольшая помеха с частотой меньше килогерца (определял на слух). На маленькой громкости она почти не заметна, но хочется совершенства. Думаю, что возможно две причины: во-первых, возможно я накосячил с разводкой платы (поставил всего два блокировочных конденсатора, плюс к этому тянул шины питания не самым оптимальным образом). Во-вторых, возможно всё дело в контроллере. На отладочной плате у меня стоял контроллер, в котором питание аналоговой части выполняется через отдельные выводы. А в этом устройстве использован 48-ми выводной контроллер, в нем питание аналоговой части и цифровой совмещены. Если причина первая, то ее можно решить модернизацией платы для следующего устройства (нужно сделать несколько), а вот если вторая причина, то нужна замена контроллера.

Ну а в целом устройство получилось неплохое. Размер платы примерно со спичечный коробок.

Теперь нужно переделать всю библиотеку слов под 16 разрядов.

Комментарии (29) на “STM32 Синтезатор речи “Элиза”. Продолжение”

  • Саня:

    Доброе время суток! круто! классные статьи! :)
    вот хочу себе МП3 плеер прикупить, но как-то в нем всегда хочется чего-то добавить! теперь точно передумаю и может руки дойдут собрать себе свой. правда на другом проце и с аудиокодеками по I2S. не сталкивались? на плате F4 установлены. но пока руки не дохадят, работа учеба!
    Большое спасибо за классный материал! :))))

    • kontroller:

      У F4 возможности конечно солиднее.
      Просто на данном этапе я ставил цель сделать «бюджетное» решение.
      F100 дешевле, поэтому остановился на нём. И воспроизведения WAV пока достаточно.
      Хочу довести до ума этот вариант, заказать печатные платы, потому что мне нужно несколько таких синтезаторов встроить в реальные устройства.
      А поработать с Ф4 хочу тоже.

  • Vlad Kerensky:

    Работаю с мк STM32F100C4T6 в связке с усилком TDA7233, использую звук 8 бит 22 кГц, качество очень хорошее и без помех. В свое время наткнулся на наводки связанные с неправильной разводкой питания, но давно исправил. Из написанного могу судить что у вас проблемы либо с разводкой либо с усилителем либо с программой.

    • kontroller:

      С усилителем вряд ли.
      Я подключал разные.
      Значит проблема в разводке или косяк в программе.

  • Vlad Kerensky:

    Если можно, покажите разводку и прогу отвечающую за воспроизведение 8 битного звука. А я пока выделю кусок своей проги кот отвечает за звук.

  • Vlad Kerensky:

    while(!(DMA1->ISR & DMA_ISR_HTIF3)); //ждем освобождение первой части буфера

    Я уже писал об этом — этот флаг будет срабатывать много много раз после заполнения половины буфера. дело в том что прерывание или проверку по заполнению половины надо отключать после первого срабатывания условия и потом включать после срабатывания условия полного обмена. Это осоьенность работы DMA всех стмок что мне попадались.

    • kontroller:

      Я слышал про этот косяк, но не предавал ему значения.
      Сейчас попробую врубиться.

      • kontroller:

        Так в моем случае это не должно влиять:
        - идет воспроизведение превой части, ждем срабатывания флага DMA_ISR_HTIF3 (тут проблем не должно быть);
        - после срабатывания DMA_ISR_HTIF3 сбрасываю его и заполняю освободившуюся часть (воспроизведение идет со второй половины);
        - жду освобождения втоой половины. Если при этом срабатывает флаг DMA_ISR_HTIF3 мне не страшно;
        - после освобождения второй половины загружаю её и начинаю всё с начала.
        Проблем не должно быть.
        Хотя…
        Если при работе со втоой половиной срабатывает флаг первой, то перейдя к началу возможны проблемы.

  • Vlad Kerensky:

    просто выключите проверку половины после первого срабатывания и включите при срабатывании проверки по полному обмену. Кстати на самом деле это не косяк а логичная работа — просто когда буфер заполняется дальше половины то половина то у него каждый раз заполнена, и это удобно в конкретных случаях.

    • kontroller:

      «Косяк» — это применительно к моему случаю.
      Сделал так:
      while(!(DMA1->ISR & DMA_ISR_TCIF3)); //ждем освобождение второй части буфера
      DMA1->IFCR |= DMA_ISR_TCIF3; //сбросить флаг
      DMA1->IFCR |= DMA_ISR_HTIF3; //сбросить флаг
      Т.е. после освобождении второй части сбрасываю оба флага. Результат тот же.
      Похоже что помеха идет с частотой обноления буфера, т.е. всякий раз, когда читается карта.
      При 16 битах частота помехи в два раза больше, чем при 8

  • Vlad Kerensky:

    так не пойдет… нужно именно отключать проверку
    while(!(DMA1->ISR & DMA_ISR_HTIF3)); //ждем освобождение первой части буфера

    то есть сделать ее с условием
    if (n = 0)
    {
    while(!(DMA1->ISR & DMA_ISR_HTIF3)); //ждем освобождение первой части буфера
    {
    // код
    n = 1;
    }
    }

    while(!(DMA1->ISR & DMA_ISR_TCIF3)); //ждем освобождение второй части буфера
    {
    //код
    n = 0;
    }

    • kontroller:

      Но ведь пока не освободится вторая половина, я к первой не перехожу.
      Осободилась вторая — сбросил оба флага — перехожу к первой.
      Прерывания не используются.
      Наверное я сегодня туго соображаю.

  • Vlad Kerensky:

    а вообще еще лучше все это сделать через прерывания и освободить ядро от натуги

    void DMA1_Channel3_IRQHandler (void)
    {
    if (DMA1->ISR & DMA_ISR_HTIF3)
    {
    DMA1_Channel3->CCR &= ~DMA_CCR3_HTIE; // запрет прерывания по половине

    //ваш код

    DMA1->IFCR |= DMA_ISR_HTIF3; // очистка флага прерывания
    }
    else if (DMA1->ISR & DMA_ISR_TCIF3) // по завершению обмена
    {
    //ваш код

    DMA1->IFCR |= DMA_IFCR_CGIF1; // отчистка флага прерывания полного обмена
    DMA1_Channel3->CCR |= DMA_CCR3_HTIE; // разрешение прерывания по половине обмена
    }

    DMA1->IFCR |= DMA_IFCR_CGIF3; // сбросить все прерывания канала 3
    }

  • Vlad Kerensky:

    модулю DMA пофиг что вы освободили первую половину — главное что вы работаете со второй — поэтому и срабатывет флаг половины обмена

    • kontroller:

      Дык, я 1)прерывания не использую; 2)дожидаюсь освобождения второй половины, сбрасываю оба флага (ДМА в это время уже начал кидать первую часть в ЦАП), загружаю вторую часть (тем временем ещё воспроизводится первая), перехожу к ожиданию освобождения первой части (и вот тут как раз и начинается анализ флага освобождения первой части)

      • kontroller:

        Провел эксперимент: загрузил в буфер 0х80 и постоянно его воспроизводил. Если при этом обращения к карте памяти не выполнять, то всё нормально. Если обращаться к карте — идёт помеха. Похоже проблема в разводке.

  • Vlad Kerensky:

    Дело в том что выставление флагов DMA_ISR_HTIF3 и DMA_ISR_TCIF3 и есть прерывания для ДМА, а то что вы их не обрабатываете а проверяете ядром — ваша проблема
    похоже вы плохо понимаете что сами написали…
    вот я смотрю на ваш код:

    while(!(DMA1->ISR & DMA_ISR_HTIF3)); //ждем освобождение первой части буфера
    DMA1->IFCR |= DMA_ISR_HTIF3; //сбросить флаг
    //заполнить освободившуюся часть буфера
    res = f_read ( &file, &DAC_Buff[0], SIZE_DAC_BUFF/2, &cnt );
    if(res) {res = WP_ERROR_READ_FILE; break;}//ошибка при чтении!!!
    if(cntISR & DMA_ISR_TCIF3)); //ждем освобождение второй части буфера
    DMA1->IFCR |= DMA_ISR_TCIF3; //сбросить флаг
    //заполнить освободившуюся часть буфера
    res = f_read ( &file, &DAC_Buff[SIZE_DAC_BUFF/2], SIZE_DAC_BUFF/2, &cnt );
    if(res) {res = WP_ERROR_READ_FILE; break;}//ошибка при чтении!!!
    if(cnt<SIZE_DAC_BUFF/2) break; //файл закончился

    и сдесь написано: проверяйтесь флаги DMA_ISR_HTIF3 и DMA_ISR_TCIF3 и если они выставлены делайте там что написано. И вот модуль ДМА идет копировать ячейки буфера которых у него 64 к примеру. Вот он скопировал 31 ячейку и копирует 32 и такой опа у меня половина готова, надо выставить DMA_ISR_HTIF3, ядро при очередной проверке — оп а DMA_ISR_HTIF3 выставлено — делаю код как в условии и обнуляю DMA_ISR_HTIF3. Затем модуль ДМА копирует 33 ячейку и такой — о, а у меня половина сделана — надо выставить DMA_ISR_HTIF3 — и вот этого как раз недолжно быть уже, как раз это и вызывает помехи.

    И кстати важно отключать проверку по половине обмена прежде чем обнулять флаг DMA_ISR_HTIF3 т.к. модуль ДМА работает независимо от ядра и пока ядро пыжытся обрабатывая одну половину, дма копирует и копирует.

  • Vlad Kerensky:

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

  • Vlad Kerensky:

    вам заместо этого стека надо вставить такой код

    if (n == 0)
    {
    while(!(DMA1->ISR & DMA_ISR_HTIF3)); //ждем освобождение первой части буфера
    {
    //заполняем первую половину
    n = 1;
    }
    }
    while(!(DMA1->ISR & DMA_ISR_TCIF3)); //ждем освобождение второй части буфера
    {
    // заполняем вторую половину
    n = 0;
    DMA1->IFCR |= DMA_IFCR_CGIF3; // сбросить все прерывания канала 3
    }
    //—————————————————-

    а еще лучше сделать прерывания как я показал выше и заместо вставок «// ваш код» поставить заполнение первой и второй половины буфера соответственно

  • Vlad Kerensky:

    еще раз проссматрел ваш код и понял что написал не то.
    у вас код пытается 32 раза перезаписать первую половину
    заместо этого стека можно попробовать:

    while(!(DMA1->ISR & DMA_ISR_HTIF3)); //ждем освобождение первой части буфера
    DMA1->IFCR |= DMA_ISR_HTIF3; //сбросить флаг
    //заполнить освободившуюся часть буфера
    res = f_read ( &file, &DAC_Buff[0], SIZE_DAC_BUFF/2, &cnt );
    if(res) {res = WP_ERROR_READ_FILE; break;}//ошибка при чтении!!!
    while(!(DMA1->ISR & DMA_ISR_TCIF3)); //ждем освобождение второй части буфера
    DMA1->IFCR |= DMA_ISR_TCIF3; //сбросить флаг
    DMA1->IFCR |= DMA_ISR_HTIF3; //сбросить флаг
    //заполнить освободившуюся часть буфера
    res = f_read ( &file, &DAC_Buff[SIZE_DAC_BUFF/2], SIZE_DAC_BUFF/2, &cnt );
    if(res) {res = WP_ERROR_READ_FILE; break;}//ошибка при чтении!!!
    if(cnt<SIZE_DAC_BUFF/2) break; //файл закончился

    • kontroller:

      Что-то у меня с соображением не то сегодня.
      Почему Вы пишете, что пытается 32 раза перезаписать?

  • Vlad Kerensky:

    я конечно извиняюсь за эту кучу, но тут какой то глюк, я вставляю один текст а он отправляет другой … причем я проверяю что отправляю и все норм, нажимаю отправить и в посте уже другой текст … мистика, может есть аська или почта ?

  • Ваша помеха, возможно оттого, что Вы для для 16-битовых данных (а эти данные — со знаком) не вызываете функцию wp_convert_data (она у Вас есть).
    Кстати, там в этой функции перевода в беззнаковое число,
    в двух местах вкралась небольшая ошибочка:
    data = data + 0x7fff; // а надо + 0×8000

  • Vlad Kerensky:

    это не обьясняет помеху при 8 битах, а я так понимаю что они связаны.

Оставить комментарий

Spam Protection by WP-SpamFree