Переходимо з STM32 на російський мікроконтролер К1986ВЕ92QI. Практичне застосування: Генеруємо і відтворюється звук. Частина перша: генеруємо прямокутний і синусоїдальний сигнал. Освоєння ЦАП (DAC)

Вступ

У попередній статті ми поговорили про налаштування тактової частоти мікроконтролера. Зараз мені хотілося б розглянути варіанти роботи зі звуком: його генерування і відтворення. По початку мені хотілося написати одну велику статтю, в якій було б розглянуто всі. Від генерації прямокутних імпульсів до відтворення FLAC з microSD картки. Але стаття вийшло просто велетенською. Так що я вирішив розбити її на кілька статтею поменше. У кожної з яких я розбираю по одному периферійного модулю.

Для тих, кому не сподобаються реалізації завдань в статті.Відразу ж обмовлюся. Вирішувати поставлені в статті завдання я буду лише з допомогою тих знань, які вже були викладені. Я розумію, що де-то більш правильним буде використання DMA, десь таймерів, і так далі. Але все це буде розглянуто далі.


Загальне уявлення про простий метод генерування звуку.

До моменту написання статті я мав лише смутне уявлення про те, як відбувається генерування звуку, але після прочитання цієї статті все встало на свої місця. З наведеної статті можна виділити саме головне — принцип.
Щоб створити звук нам потрібно змусити коливатися мембрану динаміка з певною частотою. Кожній ноті відповідає своя частота, наприклад ноті До 1 октави, відповідає частота 261Гц. Тобто дриґаючи ногою мікроконтролера, підключеної до динаміку, зі швидкістю 261 разів в секунду ми будемо чути звучання цієї ноти. Для тих хто не сильний в музичній теорії, звук ближче від 1 кгц і вище буде більше пискливий, нижче 300Гц буде басить.
На нашій платі встановлений роз'єм Jack 3.5 і підсилювач до нього. Розглянемо принципову схему.



Як ми бачимо, підсилювач підключений до контролера через пін PE0. Після перемикання джампера «DAC_OUT_SEL» на платі, ми можемо генерувати звук, який можемо почути, підключивши, наприклад, навушники до Jack-в.

Реалізація простий пищалки

Озброївшись теорією, можна спробувати написати програму, яка буде пищати з частотою ноти до першої октави.
Налаштуємо наш пін. Відразу домовимося, що код пишеться з поправкою на те, що на порту немає нічого, крім нашого підсилювача. Надалі зробити універсальну функцію не складе праці.

//---------------------------------------------------------
//Налаштовуємо вихід, підключений до підсилювача. 
//---------------------------------------------------------
#define PER_CLOCK_PORTE (1<<25) //Біт включення тактирования порту E.
#define PORT_OE_OUT_PORTE_0 (1<<0) //Вмикання цього біту переводить PORTE_0 в "вихід". 
#define ANALOG_EN_DIGITAL_PORTE_0 (1<<0) //Включаємо цифровий режим біта порту PORTE_0.
#define PWR_MAX_PORTE_0 (3<<0) //Включення даних біт перемикає PORTE_0 в режим максимальної швидкості.

#define PORT_RXTX_PORTE_0_OUT_1 (1<<0) //Маска порту для подачі "1" на вихід.

void Buzzer_out_init (void)
{
RST_CLK->PER_CLOCK |= PER_CLOCK_PORTE; //Включаємо тактування порту E.
PORTE->OE |= PORT_OE_OUT_PORTE_0; //Вихід. 
PORTE->ANALOG |= ANALOG_EN_DIGITAL_PORTE_0; //Цифровий.
PORTE->PWR |= PWR_MAX_PORTE_0; //Максимальна швидкість (близько 10 нс).
}

Тепер потрібно вирішити, з якою частотою ми будемо «пищати». В цьому мені допомогла ця таблиця частот. Для наочності представлена нижче.



Нота до ( C ) першої октави має частоту 261.63 Герца. Це означає, що за одну секунду проходить 261.63 періоду. В кожному з яких ми 2 рази змінюємо стан біта. І того нам потрібно змінювати стан біта 523.26 разів в секунду. Поділивши 1 секунду на 523.26 ми отримаємо 0,0019110958223445, що приблизно дорівнює 191*10^(-5) секунди. Це «пауза» між перемиканнями.

Тепер, коли ми знаємо приблизне значення затримки, ми можемо налаштувати SysTick таймер і затримку до нього. Налаштувати нам його потрібно на переривання раз в 10^(-5) секунди. Для цього трохи змінимо нашу функцію цієї статті. Отримаємо наступне.

void Init_SysTick (void) //Переривання раз в 10^(-5) секунди. 
{
SysTick->LOAD = (8000000/100000)-1; 
SysTick->CTRL |= CLKSOURCE|TCKINT|ENABLE;
}

volatile uint32_t Delay_dec = 0; //Переривання від SysTick таймера.
void SysTick_Handler (void)
{
if (Delay_dec) Delay_dec--;
}

void Delay (uint32_t Delay_Data) //Функція затримки на основі SysTick таймера. 
{
Delay_dec = Delay_Data;
while (Delay_dec) {};
}

Ну і тепер, нарешті, скористаємося всім вищеописаним.
int main (void)
{
Buzzer_out_init(); //Ініціалізуємо пін звукового генератора. 
Init_SysTick(); //Ініціалізуємо системний таймер для переривань раз в 10^(-5) секунди. 
while (1) //Створюємо коливання з частотою 261.63 гц.
{
PORTE->RXTX |= PORT_RXTX_PORTE_0_OUT_1; //Включаємо "1" порту, подключеному до підсилювача.
Delay(191);
PORTE->RXTX = 0;
Delay(191);
}
}

Все б добре, але тут перші граблі. Як ви могли помітити, в основній функції немає жодного рядка коду для налаштування тактирования. Контролер тактується від HSI. У нашому прикладі це призвело до того, що замість ноти до першої октави, грала нота ля малої октави (на 2 цілих тони нижче). Щоб виправити цю помилку — додамо функцію перемикання джерела тактової частоти з HSI на HSE (зовнішній кварцовий резонатор).

Про систему тактирования було розказано у цьому уроці.

int main (void)
{
HSE_Clock_ON(); //Дозволяємо використання HSE генератора. 
HSE_Clock_OffPLL(); //Налаштовуємо "шлях" сигналу і вмикаємо тактування HSE від генератора.
Buzzer_out_init(); //Ініціалізуємо пін звукового генератора. 
Init_SysTick(); //Ініціалізуємо системний таймер для переривань раз в 10^(-5) секунди. 
while (1) //Створюємо коливання з частотою 261.63 гц.
{
PORTE->RXTX |= PORT_RXTX_PORTE_0_OUT_1; //Включаємо "1" порту, подключеному до підсилювача.
Delay(191);
PORTE->RXTX = 0;
Delay(191);
}
}

Трохи ускладнимо завдання. Напишемо програму, яка буде грати нам гаму з 12 півтонів (7 білих клавіш і 5 чорних, якщо дивитися на фортепіано). Для цього створимо масив з тривалостями всіх затримок. Розраховуємо їх так само, як і попередню: 100000/частота_ноты/2 = длительность_задержки. Ділимо 100000 тому, що у нас переривання раз в 0.00001 секунди (10^(-5)).

const uint32_t MES[13] = {191, 180, 170, 161, 152, 143, 135, 128, 120, 114, 107, 101, 96};

Тепер трохи змінимо основну функцію.

int main (void)
{
HSE_Clock_ON(); //Дозволяємо використання HSE генератора. 
HSE_Clock_OffPLL(); //Налаштовуємо "шлях" сигналу і вмикаємо тактування HSE від генератора.
Buzzer_out_init(); //Ініціалізуємо пін звукового генератора. 
Init_SysTick(); //Ініціалізуємо системний таймер для переривань раз в 10^(-5) секунди. 
while (1) //Створюємо коливання з частотою 261.63 гц.
{
for (uint32_t Nambe = 0; Nambe<13; Nambe++) //Граємо по черзі ноти. 
{
for (uint32_t LoopN = 0; LoopN<MES[12-Nambe]*3; LoopN++) //Деякий час граємо кожну з них.
{
PORTE->RXTX |= PORT_RXTX_PORTE_0_OUT_1; //Включаємо "1" порту, подключеному до підсилювача.
Delay(MES[Nambe]);
PORTE->RXTX = 0;
Delay(MES[Nambe]);
}
}
}
}

Невелике пояснення до коду. Так як довжина звучання семпла (одного періоду звукової хвилі) у кожної ноти різне, то для того, щоб більш-менш звучання кожної ноти однаковим, цикл очікування складався таким чином. Число найдовшою ноти (нота з великим числом затримки) звучало «затримку раз» найкоротшою. Поясню. Нота до першої октави (191) гралася 96 * 3 раз, а нота до другої октави (96) грала 191 раз * 3. Трійка тут коефіцієнт тривалості. Далі буде розглянуто більш правильний спосіб вимірювання затримки.

Ось так виглядає наша хвиля.



Якщо по ближче придивитися, то можна побачити її недосконалість. Вона навіть близько не схожа на прямокутні імпульси.



Завантажити звуковий файл можна тут. Файл простий пищалки.

Освоєння ЦАП

Розглядаючи малюнок нашої «хвилі» і в голову не прийде, що там синусоїда. Вивчаючи питання створення синусоїди і взагалі сигналу будь-якої форми на мікроконтролері я натрапив на дану статтю. Дана стаття попадалася мені дуже давно. І тоді я прочитав її просто заради інтересу. Нині ж вона із себе представляє практичний інтерес. У статті описано створення і використання простого ЦАП (цифро-аналоговий перетворювач). ЦАП — це пристрій для перетворення значення напруги, безпосередньо, аналогову напругу на виході. Я вже було хотів збирати схему з статті, але в черговий раз вивчаючи документацію побачив пункт.
Контролер MDR_DAC...........................................................................................................................326
Це стало для мене дуже приємним сюрпризом. Ніколи раніше я не бачив ЦАП-а безпосередньо в самому мікроконтролері. Але для початку потрібно зрозуміти, чи можна підключити ЦАП до підсилювача. Для цього відкриваємо схему плати і бачимо наступне.



Висновок нашого підсилювача підключається безпосередньо до PE0, до якого підключений ЦАП. Відмінно. Можна почати налаштування. Але перед цим трохи вивчимо ЦАП.
В мікроконтролері реалізовано два ЦАП. Для включення ЦАП необхідно встановити біт Cfg_ON_DACx в 1, використовувані висновки ЦАП порту Е були налаштовані як аналогові та були відключені які-небудь внутрішні підтяжки. Обидва ЦАП можуть працювати незалежно або спільно. При незалежній роботі ЦАП (біт Cfg_SYNC_A=0) після запису даних в регістр даних DACx_DATA на виході DACx_OUT формується рівень напруги, що відповідає записаному значенням. При синхронній роботі (біт Cfg_SYNC_A=1) дані обох ЦАП можуть бути оновлені одним записом в один з регістрів DACx_DATA. ЦАП може працювати від внутрішньої опори Cfg_M_REFx=0, тоді ЦАП формує вихідний сигнал в діапазоні від 0 до напруги живлення AUCC. У режимі роботи з зовнішньою опорою Cfg_M_REFx=1 ЦАП формує вихідну напругу в діапазоні від 0 до значення DACx_REF.
Тут, можна сказати, описана вся настройка. Поглянемо на регістри.



Їх тут всього три регістра на два ЦАП. З них два регістри для зберігання значення на виході у кожного з ЦАП. Розглянемо регістр налаштування.



Приступаємо до налаштування. Так само не забуваємо про тактування DAC. Ну і для тесту виставимо на вихід максимальна напруга (0xFFF = 4095).

//---------------------------------------------------------
//ЦАП.
//---------------------------------------------------------
#define PCLK_EN_DAC (1<<18) //Маска включення тактирования ЦАП. 
#define CFG_Cfg_ON_DAC0 (1<<2) //Маска включення ЦАП1. 

void ADC_Init (void)
{
RST_CLK->PER_CLOCK |= PCLK_EN_DAC; //Включаємо тактування ЦАП.
DAC->CFG = CFG_Cfg_ON_DAC0; //Включаємо ЦАП1. Ассинхронно. Від внутрішнього джерела.
}

Далі не забудемо і про вихід. У попередньому уроці ми налаштовували його як цифровий вихід. Зараз же, згідно рекомендації, потрібно налаштувати як аналоговий.

void Buzzer_out_DAC_init (void)
{
RST_CLK->PER_CLOCK |= PER_CLOCK_PORTE; //Включаємо тактування порту E.
PORTE->OE |= PORT_OE_OUT_PORTE_0; //Вихід. 
PORTE->ANALOG = 0; //Аналоговий.
PORTE->PWR |= PWR_MAX_PORTE_0; //Максимальна швидкість (близько 10 нс).
}

Ну і додамо все це в основну функцію.

int main (void)
{
HSE_Clock_ON(); //Дозволяємо використання HSE генератора. 
HSE_Clock_OffPLL(); //Налаштовуємо "шлях" сигналу і вмикаємо тактування HSE від генератора.
Buzzer_out_DAC_init(); //Налаштовуємо порт для ЦАП.
ADC_Init(); //Налаштовуємо ЦАП.
Init_SysTick(); //Ініціалізуємо системний таймер для переривань раз в 10^(-5) секунди. 
DAC->DAC1_DATA = 0xFFF; //Завантажуємо максимум (тестовий).
while (1) 
{

}
}

Зараз, якщо заміряти напругу на піне, ми повинні отримати близько трьох вольт. АЛЕ. Цього не відбувається. На піне у нас близько 0.08 вольт. Що не є добре. Йдемо розбиратися. Насамперед я перевірив, затактирован чи ЦАП, Все було добре. Відладчик повідомляє, що всі регістри заповнені вірно. Далі я вирішив поглянути на таблицю пінів і виявив наступне.



Ось це новина. PE0 підключений не до DAC1, а до DAC2! Ось і ще одна помилка… Змінюємо функцію ЦАП-а.

//---------------------------------------------------------
//ЦАП.
//---------------------------------------------------------
#define PCLK_EN(DAC) (1<<18) //Маска включення тактирования ЦАП. 
#define CFG_Cfg_ON_DAC0 (1<<2) //Маска включення ЦАП1. 
#define CFG_Cfg_ON_DAC1 (1<<3)
void ADC_Init (void)
{
RST_CLK->PER_CLOCK |= PCLK_EN(DAC); //Включаємо тактування ЦАП.
DAC->CFG = CFG_Cfg_ON_DAC1; //Включаємо ЦАП2. Ассинхронно. Від внутрішнього джерела.
}

Пробуємо запустити. Тепер все добре. На виході 3.28 вольта. Тепер за прикладом простий пищалки спробуємо відтворити звук прямокутними імпульсами. Для цього трохи змінимо код попереднього проекту.

const uint32_t MES[13] = {191, 180, 170, 161, 152, 143, 135, 128, 120, 114, 107, 101, 96};
int main (void)
{
HSE_Clock_ON(); //Дозволяємо використання HSE генератора. 
HSE_Clock_OffPLL(); //Налаштовуємо "шлях" сигналу і вмикаємо тактування HSE від генератора.
Buzzer_out_DAC_init(); //Налаштовуємо порт для ЦАП.
ADC_Init(); //Налаштовуємо ЦАП.
Init_SysTick(); //Ініціалізуємо системний таймер для переривань раз в 10^(-5) секунди. 
while (1) 
{
for (uint32_t Nambe = 0; Nambe<13; Nambe++) //Граємо по черзі ноти. 
{
for (uint32_t LoopN = 0; LoopN<MES[12-Nambe]*3; LoopN++) //Деякий час граємо кожну з них.
{
DAC->DAC2_DATA = 0xFFF;
Delay(MES[Nambe]);
DAC->DAC2_DATA = 0; 
Delay(MES[Nambe]);
}
}
}
}

Чисто по відчуттях звук набагато преятнее, ніж у попередньому прикладі. Так і звучить набагато голосніше. Ось запис звукового сигналу. Ось файл цього проекту. А ось, для порівняння, наша хвиля.





Відступ: підсилювач на платі шалено сильно гріється. Якщо залишити його в такому режимі хвилин на 10, то він перетворюється в піч… Тому я відключаю джампер після того, як послухав звук. Так він не нагрівається.

Генерація синусоїдальної хвилі.

Розібравшись з тим, як генерувати напругу на виході різного рівня я задумався, звідки брати значення цієї напруги? Буквально відразу ж після початку пошуків я натрапив на цю статтю. В ній я знайшов найголовніше. Код одержання значень синуса хвилі. Трохи переробивши код я отримав програму, яка запитуючи довжину хвилі і частоту семпла генерує масив значень напруги для нашого коду. Ось код на Pascal ABC (Все таки потрібно готуватися до ЄДІ і часом писати і на паскалі.).

Program Sin_wav;
Var Real_Data,
PR: double; //Періуд хвилі.
samplerate: word; //Частота семпла.
wavefrequency: double;//Частота хвилі. 
Loop: word; //Лічильник.
Name: string; //Ім'я масиву.
Begin
write('Частота семпла: '); readln(samplerate); //Отримуємо дані.
write('Частота хвилі: '); readln(wavefrequency);
write('Ім'я масиву: '); readln(Name);
write('const uint16_t ', Name, '[', samplerate, '] = {');
PR:=samplerate/2; //Обчислюємо періуд.
for Loop:=0 to samplerate-1 do //-1, т. к. вважаємо 0.
Begin
Real_Data := 2047*sin(Loop*pi/PR) + 2047; //Обчислення sine-хвилі.
//Т. до. ми не можемо створити отрицательое напруга - поднимим 0 до середини.
//Таким чином 2048-1 (від 0 до 4095) = 0, а 2045 = -2.
//2047 - половина дозволу нашого ЦАП. Половина +, половина -. І 0.
write(Round(Real_Data));
if (Loop<>samplerate-1) then write(', ');
End;
write('};')
End.

Трохи поясню. Синусоїда може приймати як позитивні, так і негативні значення. Наш ЦАП може генерувати лише позитивне напруга. Так як коливання, грубо кажучи, відбуваються із-за зміни напруги, я вирішив, що 0 буде на рівні половини дозволу ЦАП. Інакше кажучи 2047 = 0, 2045 = -2, 2049 = 2. Загальна амплітуда 4095 (Якщо вести рахунок від 0). Ось приклад виконання коду, генеруючого синусоїду ноти до першої октави (по таблиці частота хвилі 261.63 Герца). Ми розіб'ємо цю синусоїду на 100 ділянок.

Нота до ( C ) першої октави, 100 ділянок.
Частота семпла: 100
Частота хвилі: 261.63
Ім'я масиву: C_4
const uint16_t C_4[100] = {2047, 2176, 2304, 2431, 2556, 2680, 2801, 2919, 3033, 3144, 3250, 3352, 3448, 3539, 3624, 3703, 3775, 3841, 3899, 3950, 3994, 4030, 4058, 4078, 4090, 4094, 4090, 4078, 4058, 4030, 3994, 3950, 3899, 3841, 3775, 3703, 3624, 3539, 3448, 3352, 3250, 3144, 3033, 2919, 2801, 2680, 2556, 2431, 2304, 2176, 2047, 1918, 1790, 1663, 1538, 1414, 1293, 1175, 1061, 950, 844, 742, 646, 555, 470, 391, 319, 253, 195, 144, 100, 64, 36, 16, 4, 0, 4, 16, 36, 64, 100, 144, 195, 253, 319, 391, 470, 555, 646, 742, 844, 950, 1061, 1175, 1293, 1414, 1538, 1663, 1790, 1918};



Експерименти з синусоїдальної хвилею.

Отримавши синусоїду розбиту на 100 частин, згадуємо, що ця синусоїда повинна бути програна з частотою 261,63 герца. Тепер розрахуємо інтервал переривання. Секунда/(100 частин * 261, 63) = 0.00003822191 секунди. Ну щож. Скажу відразу. Я провів море експерементів, щоб отримати звук. Коротко розповім про них. Так як частоти в 8 Мгц вже явно не вистачало для такої швидкості, то я вирішив побалувати себе і розігнав чіп до 80 МГц, сподіваючись, що цього мені точно вистачить. Але не тут то було. Налаштувавши переривання SysTick на 10000000 разів в секунду, контролер навіть не доходив до циклу, в якому виводилися дані. Після я вирішив, що куди простіше буде видавати дані відразу в перериванні. Вийшло наступне.

void Init_SysTick (void) //Переривання 10000000 разів в секунду. 
{
SysTick->LOAD = (80000000/10000000)-1; 
SysTick->CTRL |= CLKSOURCE|TCKINT|ENABLE;
}

const uint16_t C_4[100] = {2047, 2176, 2304, 2431, 2556, 2680, 2801, 2919, 3033, 3144, 3250, 3352, 3448, 3539, 3624, 3703, 3775, 3841, 3899, 3950, 3994, 4030, 4058, 4078, 4090, 4094, 4090, 4078, 4058, 4030, 3994, 3950, 3899, 3841, 3775, 3703, 3624, 3539, 3448, 3352, 3250, 3144, 3033, 2919, 2801, 2680, 2556, 2431, 2304, 2176, 2047, 1918, 1790, 1663, 1538, 1414, 1293, 1175, 1061, 950, 844, 742, 646, 555, 470, 391, 319, 253, 195, 144, 100, 64, 36, 16, 4, 0, 4, 16, 36, 64, 100, 144, 195, 253, 319, 391, 470, 555, 646, 742, 844, 950, 1061, 1175, 1293, 1414, 1538, 1663, 1790, 1918};
volatile uint16_t Loop = 0;
volatile uint32_t Delay_dec = 0; //Переривання від SysTick таймера.
void SysTick_Handler (void)
{
Delay_dec++; if (Delay_dec==(382-1))
{
DAC->DAC2_DATA = C_4[Loop];
if (Loop<99) Loop++; else Loop = 0;
Delay_dec=0;
}
}

Основна функція мала вигляд:

int main (void)
{
HSE_Clock_ON(); //Дозволяємо використання HSE генератора. 
HSE_Clock_OffPLL(); //Налаштовуємо "шлях" сигналу і вмикаємо тактування HSE від генератора.
Buzzer_out_DAC_init(); //Налаштовуємо порт для ЦАП.
ADC_Init(); //Налаштовуємо ЦАП.
HSE_PLL(10); //8 Мгц -> 80 Мгц. 
Init_SysTick(); //Ініціалізуємо системний таймер для переривань.
while (1) 
{

}
}

Звук виходив таким:



При детальному розгляді видно наступне:



Ось наближене зростання «синусоїди»:



Видна величезна похибка. А так же звук вийшов дуже низьким. Може бути ля малої октави. Не вище. Що свідчить про те, що код в перериванні просто не встигає виконуватися. Навіть при частоті 80 Мгц. Зробимо інакше. Трохи зменшимо якість. Зробимо переривання трохи рідше. І округлимо цикл очікування переривання. Отримуємо наступне.

void Init_SysTick (void) //Переривання 10000000 разів в секунду. 
{
SysTick->LOAD = (80000000/1000000)-1; 
SysTick->CTRL |= CLKSOURCE|TCKINT|ENABLE;
}

const uint16_t C_4[100] = {2047, 2176, 2304, 2431, 2556, 2680, 2801, 2919, 3033, 3144, 3250, 3352, 3448, 3539, 3624, 3703, 3775, 3841, 3899, 3950, 3994, 4030, 4058, 4078, 4090, 4094, 4090, 4078, 4058, 4030, 3994, 3950, 3899, 3841, 3775, 3703, 3624, 3539, 3448, 3352, 3250, 3144, 3033, 2919, 2801, 2680, 2556, 2431, 2304, 2176, 2047, 1918, 1790, 1663, 1538, 1414, 1293, 1175, 1061, 950, 844, 742, 646, 555, 470, 391, 319, 253, 195, 144, 100, 64, 36, 16, 4, 0, 4, 16, 36, 64, 100, 144, 195, 253, 319, 391, 470, 555, 646, 742, 844, 950, 1061, 1175, 1293, 1414, 1538, 1663, 1790, 1918};
volatile uint16_t Loop = 0;
volatile uint32_t Delay_dec = 0; //Переривання від SysTick таймера.
void SysTick_Handler (void)
{
Delay_dec++; if (Delay_dec==(38-1))
{
DAC->DAC2_DATA = C_4[Loop];
if (Loop<99) Loop++; else Loop = 0;
Delay_dec=0;
}
}

Тепер переривання встигає обробитися. Ми отримуємо звук практично ідентичний з нотою До. Але все ж на слух (при порівнянні з фортепіано) можна почути неточність. Прослухати можна тут. Файл проекту тут.
Наша звукова хвиля має наступний вигляд:



Підйом «синусоїди» має наступний вигляд:



Як ми бачимо, толку в наших 100 частинах немає. ЦАП просто не встигає міняти напруга. (Як мені здалося на момент вивчення.) Змінимо наш проект так, щоб синусоїда складалася з 20 частин. Отримуємо наступний масив.

Частота семпла: 20
Частота хвилі: 261.63
Ім'я масиву: C_4
const uint16_t C_4[20] = {2047, 2680, 3250, 3703, 3994, 4094, 3994, 3703, 3250, 2680, 2047, 1414, 844, 391, 100, 0, 100, 391, 844, 1414};

Розрахуємо частоту переривання тепер. Секунду/(20 частин * 261.63) = 0.00019110958 секунди ~ 191*10^(-6). Це вже краще, ніж було раніше. Налаштовуємо переривання і затримку. Отримуємо наступне.

void Init_SysTick (void) //Переривання 1000000 разів в секунду. 
{
SysTick->LOAD = (80000000/1000000)-1; 
SysTick->CTRL |= CLKSOURCE|TCKINT|ENABLE;
}

const uint16_t C_4[20] = {2047, 2680, 3250, 3703, 3994, 4094, 3994, 3703, 3250, 2680, 2047, 1414, 844, 391, 100, 0, 100, 391, 844, 1414};
volatile uint16_t Loop = 0;
volatile uint32_t Delay_dec = 0; //Переривання від SysTick таймера.
void SysTick_Handler (void)
{
Delay_dec++; if (Delay_dec==(191-1))
{
DAC->DAC2_DATA = C_4[Loop];
if (Loop<19) Loop++; else Loop = 0;
Delay_dec=0;
}
}

Ми отримали звук ще більш наближений до ноті До. Звук можна взяти тут.

Поглянемо на хвилю:




На мій подив переді мною знову практично прямокутні імпульси! Хоча повинна була бути синусоїда. Десь я помилився… «А що, якщо знизити амплітуду коливання?» — подумав я. Змінив у програму на паскалі параметр, що показує висоту хвилі» від «0» до «межі» з 2047 на 1500. Але це ні до чого не призвело. І тут я глянув на меню програми детальніше і побачив.



Від -1 вольта до 1 вольта! Інакше кажучи, амплітуда 2 вольта! А у мене було 3 + підсилювач! Мені було ліньки шукати документацію на підсилювач, тому, шляхом підбору я дізнався, що ідеальна амплітуда — 70*2. Ось код змінений програми на паскалі.

Program Sin_wav;
Var Real_Data,
PR: double; //Періуд хвилі.
samplerate: word; //Частота семпла.
wavefrequency: double;//Частота хвилі. 
Loop: word; //Лічильник.
Name: string; //Ім'я масиву.
Begin
write('Частота семпла: '); readln(samplerate); //Отримуємо дані.
write('Частота хвилі: '); readln(wavefrequency);
write('Ім'я масиву: '); readln(Name);
write('const uint16_t ', Name, '[', samplerate, '] = {');
PR:=samplerate/2; //Обчислюємо періуд.
for Loop:=0 to samplerate-1 do //-1, т. к. вважаємо 0.
Begin
Real_Data := 70*sin(Loop*pi/PR) + 2047; //Обчислення sine-хвилі.
//Т. до. ми не можемо створити отрицательое напруга - поднимим 0 до середини.
//Таким чином 2048-1 (від 0 до 4095) = 0, а 2045 = -2.
//2047 - половина дозволу нашого ЦАП. Половина +, половина -. І 0.
write(Round(Real_Data));
if (Loop<>samplerate-1) then write(', ');
End;
write('};')
End.

Ось вдалий масив:

const uint16_t C_4[20] = {2047, 2069, 2088, 2104, 2114, 2117, 2114, 2104, 2088, 2069, 2047, 2025, 2006, 1990, 1980, 1977, 1980, 1990, 2006, 2025};

Аудіозапис. Тепер поглянемо на наш сигнал. Нарешті щось схоже на синусоїду!



Тепер, коли я зміг створити синусоїду з 20 частин, скористаємося кодом, розглянутих раніше, і спробуємо зробити синусоїду з 100 частин. Ось одержаний масив.

Частота семпла: 100
Частота хвилі: 261.63
Ім'я масиву: C_4
const uint16_t C_4[100] = {2047, 2051, 2056, 2060, 2064, 2069, 2073, 2077, 2081, 2085, 2088, 2092, 2095, 2098, 2101, 2104, 2106, 2108, 2110, 2112, 2114, 2115, 2116, 2116, 2117, 2117, 2117, 2116, 2116, 2115, 2114, 2112, 2110, 2108, 2106, 2104, 2101, 2098, 2095, 2092, 2088, 2085, 2081, 2077, 2073, 2069, 2064, 2060, 2056, 2051, 2047, 2043, 2038, 2034, 2030, 2025, 2021, 2017, 2013, 2009, 2006, 2002, 1999, 1996, 1993, 1990, 1988, 1986, 1984, 1982, 1980, 1979, 1978, 1978, 1977, 1977, 1977, 1978, 1978, 1979, 1980, 1982, 1984, 1986, 1988, 1990, 1993, 1996, 1999, 2002, 2006, 2009, 2013, 2017, 2021, 2025, 2030, 2034, 2038, 2043};

Замінюємо масив використовуючи старий код та отримуємо це:



Ми отримали дуже якісну синусоїду! Послухати можна тут. Завантажити проект тут.

Ліричний відступ

Під час запису звуку з амплітудою >3 Вольт на колонках, підключених до тієї ж самої звукової карти ноутбука, з'являвся трохи змінений звук з пристрою. По початку я думав, що це через включеної фоном програми. Але як тільки до мене дійшло знизити амплітуду — зрозумів, що ні. Так як звук пропав. Підозрюю, що ще трохи і я б спалив звукову карту.

Для запису і аналізу звуку використав повністю безкоштовну програму Audacity. Вона дозволяє записувати звук в як завгодно високій якості без обмежень і дозволяє зберігати його в будь-якому форматі (у тому числі і FLAC, в якому я і наводив приклади).

Так само хочу додати, що у мене немає осцилографа. Ця програма замінила мені його.

Замість висновку.

В наступній статті буде розібраний DMA модуль і його відмінювання з DAC. Файли до уроків.

Список попередніх статей.1. Переходимо з STM32F103 на К1986ВЕ92QI. Або перше знайомство з російським мікроконтролером.
2. Переходимо з STM32 на російський мікроконтролер К1986ВЕ92QI. Налаштування проекту в keil і миготіння світлодіодом.
3. Переходимо з STM32 на російський мікроконтролер К1986ВЕ92QI. Системний таймер (SysTick).
4. Переходимо з STM32 на російський мікроконтролер К1986ВЕ92QI. Налаштування тактової частоти.


Джерело: Хабрахабр

0 коментарів

Тільки зареєстровані та авторизовані користувачі можуть залишати коментарі.