Многоканальный DDS генератор на STM32

Библитека позволяет генерировать сигналы нескольких форм (синус, прямоугольник, треугольник, пила), и микшировать произвольное количество каналов:
Синус 50+440+1500 Гц

.h: codetidy.com/4013/
.c: codetidy.com/4014/

Генерация — по принципу DDS, с 16-битным аккумулятором фазы.
Количество каналов определяется константой AUDIO_GEN_CHANNELS, количество тонов на канал — AUDIO_GEN_TONES, частота дискретизации — AUDIO_GEN_F.

Настройки каналов хранятся в массиве audio_gen_channels[AUDIO_GEN_CHANNELS].

Структуры настроек:
//описание тона
struct audio_gen_tone_struct {
	enum audio_gen_waveform waveform; //форма
	U8  volume;        //громкость (пока не используется)
	U16 phase;         //аккумулятор фазы
	U16 phase_inc;     //инкремент аккумулятора фазы
	U8  mix;           //=1 для микширования этого тона с другими

	U16 out;           //выход

	U8  update; //=1 для обновления параметров
	enum audio_gen_waveform waveform_new; //новая форма
	U16 phase_inc_new; //новый инкремент фазы
	U8  mix_new;
};
//описание канала
struct audio_gen_channel_struct {
	//тоны
	struct audio_gen_tone_struct tone[AUDIO_GEN_TONES];
	S16 out; //выход микшера
};


Итак, для работы нужно определить несколько констант:
#define AUDIO_GEN_F        44100 //частота дискр.
#define AUDIO_GEN_CHANNELS 1 //кол.каналов
#define AUDIO_GEN_TONES    3 //кол.тонов на канал

Настройки отдельных тонов определяет функция audio_gen_tone_set:
//канал 0, тон 0, синус, 440Гц, микшировать
audio_gen_tone_set(0,0,AUDIO_GEN_SINE,440,1);
//0,1,синус,50Гц, микшировать
audio_gen_tone_set(0,1,AUDIO_GEN_SINE,50,1);
//0,2,синус,1500Гц, микшировать
audio_gen_tone_set(0,2,AUDIO_GEN_SINE,1500,1);

Форма сигнала задается константами:
enum audio_gen_waveform {
  AUDIO_GEN_NONE,AUDIO_GEN_SINE,AUDIO_GEN_SQUARE,AUDIO_GEN_TRIANGLE,AUDIO_GEN_SAWTOOTH
};

Для синуса используется таблица четверти периода на 64 отсчета sine_tab, остальные сигналы рассчитываются без таблиц.

Расчет инкремента фазы для базовой частоты base_freq, и генерируемой частоты tone_freq — функция audio_gen_phase_inc:
U16 audio_gen_phase_inc(U16 base_freq, U16 tone_freq) {
	return 65536UL * tone_freq / base_freq;
}

Перерасчет выходов выполняет функция audio_gen_update, которую нужно вызывать из прерывания таймера з заданной частотой AUDIO_GEN_F.

В результате выход каждого тона попадает в audio_gen_channels[c].tone[t].out, выход микшера — в audio_gen_channels[c].out.

Остается этот результат вывести на выход ЦАПа.

Примеры сигналов:
  • треугольник 440Гц:
  • треугольники 50+440Гц:
  • синусы 50+440+1500Гц:
  • треугольник 50Гц + синус 440Гц:
  • меандр 50Гц + пила 440Гц:


Пример настройки таймера и ЦАПа STM32F100/24MHz, программное обновление ЦАПа (можно пойти дальше, и писать отсчеты в буфер, из буфера обновлять ЦАП через DMA):
//таймер
#define F_CPU 24000000UL //частота контроллера
#define F_TIMER 24000UL //частота прерываний таймера 4
#define AUDIO_GEN_F F_TIMER //частота генератора
#define AUDIO_GEN_CHANNELS 1 //кол.каналов
#define AUDIO_GEN_TONES    3 //кол.тонов на канал
void setup_timer(void) {
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM4, ENABLE);

	TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
	TIM_TimeBaseStructInit(&TIM_TimeBaseStructure);
	TIM_TimeBaseStructure.TIM_Period        = F_CPU / F_TIMER - 1;
	TIM_TimeBaseStructure.TIM_Prescaler     = 0;
	TIM_TimeBaseStructure.TIM_ClockDivision = 0;
	TIM_TimeBaseStructure.TIM_CounterMode   = TIM_CounterMode_Up;
	TIM_TimeBaseInit(TIM4, &TIM_TimeBaseStructure);

	TIM_Cmd(TIM4, ENABLE);
	TIM_ITConfig(TIM4, TIM_IT_Update, ENABLE);
	NVIC_EnableIRQ(TIM4_IRQn);
}

//ЦАП
void setup_dac(void) {
	//ЦАП1 = PA4
	GPIO_InitTypeDef gpio;
	GPIO_StructInit(&gpio);
	gpio.GPIO_Pin   = GPIO_Pin_4;
	gpio.GPIO_Mode  = GPIO_Mode_AIN;
	gpio.GPIO_Speed = GPIO_Speed_2MHz;
	GPIO_Init(GPIOA, &gpio);

	RCC_APB1PeriphClockCmd(RCC_APB1Periph_DAC, ENABLE);

	DAC_InitTypeDef dac;
	DAC_StructInit(&dac);
	dac.DAC_Trigger = DAC_Trigger_None;
	dac.DAC_OutputBuffer = DAC_OutputBuffer_Enable;
	dac.DAC_WaveGeneration = DAC_WaveGeneration_None;
	DAC_Init(DAC_Channel_1, &dac);

	DAC_Cmd(DAC_Channel_1, ENABLE);
}

//прерывание таймера 4
void handler_TIM4_IRQn(void) {
	TIM_ClearITPendingBit(TIM4, TIM_IT_Update);

	//записать ЦАП
	DAC_SetChannel1Data(DAC_Align_12b_L, audio_gen_channels[0].out);
	//обновить выходы генератора
	audio_gen_update();
}

//генерация 3х частот разной формы:
void setup_audio(void) {
	audio_gen_tone_set(0,0,AUDIO_GEN_SINE,440,1);
	audio_gen_tone_set(0,1,AUDIO_GEN_TRIANGLE,100,1);
	audio_gen_tone_set(0,2,AUDIO_GEN_SAWTOOTH,1000,1);
}


Внизу прикреплена программа генерации с записью результата в .raw файл, который можно открыть в любом редакторе как Signed 16-bit, big-endian.

Можно написать парсер MML или RTTTL — получится неплохая озвучка для устройств на МК.
  • +6
  • 18 ноября 2012, 20:11
  • reptile
  • 1
Файлы в топике: ul_audio_gen.zip

Комментарии (19)

RSS свернуть / развернуть
.h: codetidy.com/4013/
.c: codetidy.com/4014/
И все же, почему не прикрепить эти файлы к топику, в архиве? Так удобней же (для читателей).
Для синуса используется таблица четверти периода на 64 отсчета sine_tab
А не пробовал применить недавно обсуждавшийся тут простой генератор синуса? Что-то вроде X[n+1] = X[n-1] — K*X[n] (мог перепутать порядок отсчетов).

И еще по поводу картинок. Желательно, во первых, убрать у них align'ы (если есть, но у тебя вроде нет, они на самом деле задают обтекание картинки текстом), а во вторых — отделить их от текста переводами строки:
blablabla <img ...> blablabla //неправильно

blablabla //правильно
<img ...>
blablabla
+1
  • avatar
  • Vga
  • 19 ноября 2012, 05:56
И все же, почему не прикрепить эти файлы к топику, в архиве?
архив прикреплен
простой генератор синуса? Что-то вроде X[n+1] = X[n-1] — K*X[n] (мог перепутать порядок отсчетов)
где он описан? табличный метод и так довостаточно быстрый
0
Если бы я помнил — я бы дал ссылку)
Табличный метод вероятно даже быстрее будет, но этот даст лучше разрешение на низких частотах и займет меньше памяти.
архив прикреплен
По описанию так сразу и не скажешь, что библиотечка там тоже есть.
0
Весьма интересно.

Правда, только к концу статьи понял, что речь идёт о STM32 ><
0
библиотека никак не привязана к STM32, работает на любой архитектуре
0
А, ну, значит, я и этого не понял ^^"

Саму библиотеку я не смотрел.
0
А какая максимальная частота пилы и синусоиды физически реализуема?
0
на 32F100 примерно под 50кГц, с оптимизацией кода и DMA — 100..150.
на 32F103 и тем более F2/F3/F4 — в несколько раз больше.
0
Спасибо. А это частота самого сигнала, или частота дискретизации?
0
Сигнала. Макс.скорость установки выхода ЦАПа примерно 300кГц для прямоугольника, для плавно нарастающего сигнала типа синуса/треугольника — в 2..4 раза выше. Если взять минимум 10 точек на период — получится примерно 100 кГц.
0
возможно, глупость скажу: надо было снабдить каждую картинку звуковым файлом.
я правильно понимаю, что там, где три частоты смикшированы — можно услышать три звука: что-то там из контроктавы, ля первой октавы и что-то там в верхах?
0
Звук будет один — просто аккордом… ну и такого рода звуки очень своеобразно звучат — обертонов нет, звучание ооочень своеобрзное
0
правильно.
для большей достоверности в буфер волны можно записать звук реального инструмента.
что в принципе и делают в Wavetable-синтезаторах и всяких chiptune-плеерах/трекерах.
0
и в электророялях. только вот не пойму. там что, на каждую клавишу — свой сэмпл? и как с нюансами — стаккато/легато/фортиссимо/пианиссимо?
0
свой семпл на каждый инструмент.
тон меняется настройкой DDS, нюансы — применением фильтров.

en.wikibooks.org/wiki/Sound_Synthesis_Theory
en.wikipedia.org/wiki/Distortion_synthesis
en.wikipedia.org/wiki/Waveshaper

буки по теме: 1, 2, 3, 4, 5, 6
0
чтоб не заблудиться в куче инфы. семпл — только на инструмент, или на каждую ноту(клавишу)? откуда там берутся банки по десятку гигабайт — и это один инструмент. что это?
0
0
семпл — только на инструмент, или на каждую ноту(клавишу)?
На каждый инструмент — свой набор сэмплов, это само собой разумеется.
Существуют инструменты с несколькими регистрами, например, клавесин (два-три регистра) или орган (несколько десятков или даже более сотни регистров).
Весь диапазон инструмента разбивается на несколько участков и сэмплируется каждый участок из-за того, что на низких и на высоких нотах тембровая окраска почти наверняка будет различаться.
0
Мне кажется, MikeSmith должен знать получше, у него есть публикации на тему проигрывания музыки.
0
Только зарегистрированные и авторизованные пользователи могут оставлять комментарии.