DDS-генератор синусоидального сигнала

В данном проекте рассмотрим изготовление генератора синусоидального сигнала при помощи метода прямого синтеза (DDS-метод). Для реализации этого проекта нам не потребуется какого-либо дополнительного оборудования кроме самого контроллера Arduino. Частотный диапазон генератора от 0 до 16 кГц, с точностью до 1 мкГц! Данное устройство может пригодится не только для генерирования звуковых сигналов, но в тестовом и измерительном оборудовании радиолюбителя. Например в телекоммуникационном оборудовании DDS генератор можно использовать для ЧМ и ФМ модуляции (FSK и PSK).
DDS-метод
В программной части проекта, для реализации DDS метода, нам понадобится 4 вещи:
аккумулятор и tuning word, который в нашем случае состоит из двух long integer переменных;
таблица значений синусоидального сигнала (один период);
цифро-аналоговый преобразователь, который обеспечивается внутренним ШИМ Arduino (analogWrite);
генератор тактовых импульсов (используем внутренний hard-таймер от ATMega).
Большинство значащих байт аккумулятор используется для адресов таблицы синусоидального сигнала. Весь циклический процесс, работает по прерыванию от внутреннего тактового генератора.
Программное обеспечение
Для работы данного скетча на Arduino Diecimila или Duemilenove подключите потенциометр к аналоговому выводу 0 и к GND и +5В. Выход генератора находится на выводе 11, куда вы можете подключить активные колонки, или ФНЧ фильтр описанный ниже.
/* * * DDS Sine Generator mit ATMEGS 168 * Timer2 generates the 31250 KHz Clock Interrupt * * KHM 2009 / Martin Nawrath * Kunsthochschule fuer Medien Koeln * Academy of Media Arts Cologne */ #include "avr/pgmspace.h" // table of 256 sine values / one sine period / stored in flash memory PROGMEM prog_uchar sine256[] = < 127,130,133,136,139,143,146,149,152,155,158,161,164,167,170,173,176,178,181,184,187,190,192,195,198,200,203,205,208,210,212,215,217,219,221,223,225,227,229,231,233,234,236,238,239,240, 242,243,244,245,247,248,249,249,250,251,252,252,253,253,253,254,254,254,254,254,254,254,253,253,253,252,252,251,250,249,249,248,247,245,244,243,242,240,239,238,236,234,233,231,229,227,225,223, 221,219,217,215,212,210,208,205,203,200,198,195,192,190,187,184,181,178,176,173,170,167,164,161,158,155,152,149,146,143,139,136,133,130,127,124,121,118,115,111,108,105,102,99,96,93,90,87,84,81,78, 76,73,70,67,64,62,59,56,54,51,49,46,44,42,39,37,35,33,31,29,27,25,23,21,20,18,16,15,14,12,11,10,9,7,6,5,5,4,3,2,2,1,1,1,0,0,0,0,0,0,0,1,1,1,2,2,3,4,5,5,6,7,9,10,11,12,14,15,16,18,20,21,23,25,27,29,31, 33,35,37,39,42,44,46,49,51,54,56,59,62,64,67,70,73,76,78,81,84,87,90,93,96,99,102,105,108,111,115,118,121,124 >; #define cbi(sfr, bit) (_SFR_BYTE(sfr) &= ~_BV(bit)) #define sbi(sfr, bit) (_SFR_BYTE(sfr) |= _BV(bit)) int ledPin = 13; // LED pin 7 int testPin = 7; int t2Pin = 6; byte bb; double dfreq; // const double refclk=31372.549; // =16MHz / 510 const double refclk=31376.6; // measured // variables used inside interrupt service declared as voilatile volatile byte icnt; // var inside interrupt volatile byte icnt1; // var inside interrupt volatile byte c4ms; // counter incremented all 4ms volatile unsigned long phaccu; // pahse accumulator volatile unsigned long tword_m; // dds tuning word m void setup() < pinMode(ledPin, OUTPUT); // sets the digital pin as output Serial.begin(115200); // connect to the serial port Serial.println("DDS Test"); pinMode(6, OUTPUT); // sets the digital pin as output pinMode(7, OUTPUT); // sets the digital pin as output pinMode(11, OUTPUT); // pin11= PWM output / frequency output Setup_timer2(); // disable interrupts to avoid timing distortion cbi (TIMSK0,TOIE0); // disable Timer0 . delay() is now not available sbi (TIMSK2,TOIE2); // enable Timer2 Interrupt dfreq=1000.0; // initial output frequency = 1000.o Hz tword_m=pow(2,32)*dfreq/refclk; // calulate DDS new tuning word >void loop() < while(1) < if (c4ms >250) < // timer / wait fou a full second c4ms=0; dfreq=analogRead(0); // read Poti on analog pin 0 to adjust output frequency from 0..1023 Hz cbi (TIMSK2,TOIE2); // disble Timer2 Interrupt tword_m=pow(2,32)*dfreq/refclk; // calulate DDS new tuning word sbi (TIMSK2,TOIE2); // enable Timer2 Interrupt Serial.print(dfreq); Serial.print(" "); Serial.println(tword_m); >sbi(PORTD,6); // Test / set PORTD,7 high to observe timing with a scope cbi(PORTD,6); // Test /reset PORTD,7 high to observe timing with a scope > > //****************************************************************** // timer2 setup // set prscaler to 1, PWM mode to phase correct PWM, 16000000/510 = 31372.55 Hz clock void Setup_timer2() < // Timer2 Clock Prescaler to : 1 sbi (TCCR2B, CS20); cbi (TCCR2B, CS21); cbi (TCCR2B, CS22); // Timer2 PWM Mode set to Phase Correct PWM cbi (TCCR2A, COM2A0); // clear Compare Match sbi (TCCR2A, COM2A1); sbi (TCCR2A, WGM20); // Mode 1 / Phase Correct PWM cbi (TCCR2A, WGM21); cbi (TCCR2B, WGM22); >//****************************************************************** // Timer2 Interrupt Service at 31372,550 KHz = 32uSec // this is the timebase REFCLOCK for the DDS generator // FOUT = (M (REFCLK)) / (2 exp 32) // runtime : 8 microseconds ( inclusive push and pop) ISR(TIMER2_OVF_vect) < sbi(PORTD,7); // Test / set PORTD,7 high to observe timing with a oscope phaccu=phaccu+tword_m; // soft DDS, phase accu with 32 bits icnt=phaccu >> 24; // use upper 8 bits for phase accu as frequency information // read value fron ROM sine table and send to PWM DAC OCR2A=pgm_read_byte_near(sine256 + icnt); if(icnt1++ == 125) < // increment variable c4ms all 4 milliseconds c4ms++; icnt1=0; >cbi(PORTD,7); // reset PORTD,7 >
Результат
Ниже представлена осциллограмма, на верхней части которой изображен ШИМ-сигнал на 11 выходе, а в нижней части этот же сигнал после фильтра низких частот (ФНЧ). Синусоида выглядит не очень чистой, но это в основном из-за ограниченной разрешающей способности цифрового осциллографа.
Спектрограмма показала неожиданно хороший результат. Большой пик — это на частоте около 1000 Гц. Все нежелательные искажения находятся ниже 50 дБ, возникшие из-за того, что использовался 8 битный ЦАП (1/256 = 48 дБ). 
Выходной фильтр низких частот

Для начала, вы можете подсоединить 11 пин контроллера к активным колонкам. Но скорее всего, вам еще понадобится ФНЧ-фильтр, который также будет отфильтровывать частоту дискретизации 32 кГц. Ниже представлена схема такого фильтра с частотой среза 12 кГц.
Аппаратная реализация DDS
Данная программная реализация алгоритма DDS имеет некоторые недостатки, связанные с ограниченной скоростью алгоритма программы, а также возможностями микроконтроллера ATMega. Специализированные DDS-микросхемы лишены этих недостатков и покрывают диапазон от 0 до 100 МГц.
WSPR
Извещатель о прохождении слабого сигнала (Weak Signal Propagation Reporter) — программное обеспечение позволяющее передавать и принимать сигналы радиомаяков, задействуя не только передатчик, но и интернет. При помощи данного DDS-генератора можно генерировать 4 тоновых последовательности частотой 1497.8 1499.3 1500.7 1502.2 Гц. Оригинал статьи на английском языке (перевод Колтыков А.В. для сайта cxem.net) Оригинал статьи
Теги:
Колтыков А.В.
Опубликована: 2011 г.
0
0
Вознаградить Я собрал 0 0
Оценить статью
- Техническая грамотность
Генератор сигналов на Arduino
Генератор сигнала (т.н. функциональный генератор) может быть использован для тестирования и отладки схем. Я часто использую его для проверки частотных характеристик электронных компонентов, например ОУ и датчиков. Этот генератор сигналов построен на плате Arduino. Он может выдавать четыре типа сигнала: синусоидальный, треугольный, прямоугольный и пилообразный, частота каждого из которых может регулироваться от 1Гц до 50 кГц. Частота, длительность импульса и амплитуда (усиление) сигналов управляется тремя потенциометрами. Я также добавил опциональный светодиодный индикатор, который указывает какой сигнал сейчас на выходе. 
Материалы: Термоусадка.
Провод №22.
Припой.
Дрель со сверлами.
Термоклей.
Клей
Подготовка Arduino Proto Shield

Arduino Proto Shield — это удобный способ добавления своей схемы к Arduino, но я решил немного его урезать, чтобы он занимал меньше места в корпусе. Сначала я укоротил выводы при помощи кусачек. Потом я убрал шести контактный разъем. После этого я удалил разъемы с верхней части платы.
Корпус
Я решил использовать лазерный резак для изготовления корпуса. Я разработал корпус используя AutoCAD, Autodesk 123D Make, и Corel Draw. Все файлы проекта можно скачать внизу статьи. Если у вас нет доступа к лазерному резаку, вы можете сделать все детали корпуса вручную по двумерным чертежам.
На картинке показаны отверстия на передней панели:
(3x) 7мм отверстие для потенциометров усиления, частоты и ШИМ.
(3x) 7мм отверстие для четырех кнопок — синусоидальный, треугольный, прямоугольный и пилообразный сигналы.
(1x) 10мм отверстие для аудио разъема.
Я вырезал изображения всех четырех сигналов для того чтобы их можно было подсвечивать, но вы можете просверлить простые отверстия 5мм для светодиодов под каждой кнопкой.
Также есть прямоугольное (высота11мм, ширина 12 мм) отверстие для USB-порта Arduino в задней части устройства. Я сделал корпус из дерева, поэтому мне пришлось склеивать все его части кроме задней панели, которое я приклею после сборки устройства. 
Пайка проводов к кнопкам

Припаяйте 10 кОм резистор к одному из выводов каждой кнопки. Припаяйте зеленый провод к месту соединения кнопки и резистора и красный провод к резистору как показано на фото. Черный провод припаяйте к другому контакту кнопки. Все эти соединения надо заизолировать термоусадкой во избежание короткого замыкания.
Установка аудио разъема
Свинтите пластиковый корпус с аудио разъема. Припаяйте красный провод к двум стерео контактам и черный провод к GND как показано на фотографии. Я использовал термоклей для предотвращения короткого замыкания и дополнительной фиксации проводов и пайки. После этого, вставьте гнездо в отверстие в корпусе и закрепите его термоклеем.

Установка кнопок

Снимите с кнопок верхнюю часть и установите их в корпус, зафиксировав термоклеем. После его высыхания, установите верхнюю часть кнопок обратно.
R2R ЦАП на Arduino Shield
Припаяйте восемь резисторов 20кОм на Arduino Proto Shield. Один из выводов каждого резистора должен быть подключен к цифровым контактам Arduino 0-7.
Припаяйте семь резисторов 10кОм на Arduino Proto Shield так, чтобы они были между выводами ранее припаянных восьми резисторов 20 кОм.
Припаяйте резистор 10кОм на Arduino Proto Shield так, чтобы один вывод резистора 10кОм был присоединён к цифровому контакту 0 Arduino, а другой вывод к GND. 
Панелька для микросхем
Использовать панельки для микросхем хорошо, потому что благодаря им микросхема не перегревается при пайке и может быть легко заменена в случае поломки. Припаяйте панельку для микросхемы, как показано на фотографии.
Фильтр нижних частот В качестве ФНЧ (Фильтр Нижних Частот) выступают резистор и конденсатор, соединенные последовательно. ФНЧ пропускает низкие частоты и подавляет ступеньки на сигнале. Вот как я рассчитал номиналы компонентов в своем ФНЧ:
Частота среза = 1/(2*pi*R*C) Согласно теореме Найквиста, сигналы не может иметь частоту больше чем половина частоты дискретизации. Если бы я использовал частоту дискретизации 100 кГц, то максимальная возможная частота была бы 50 кГц. Если я использую резистор 300 Ом, и хочу иметь частоту среза 50 кГц:
50000 = 1/(6.28*300*C)
C = 1.06*10^-8 F
Если немного округлить:
C = 0.01 мкФ
Подключите один вывод резистора 300 Ом к резистору 10 кОм, подключенному к цифровому выводу 7. Подключите конденсатор к другому выводу резистора 300 Ом. Второй вывод конденсатора подключается к GND.
Усилитель
Подключите положительный вывод конденсатора 220мкФ к соединению резистора и конденсатора в ФНЧ. Второй вывод конденсатора 220мкФ подключается к резистору 20 кОм, второй вывод которого подключается к 3 выводу панельки для микросхемы. Резистор 4.7 кОм подключается между 3 и 4 контактами панельки. К 4 выводу панельки подключается GND.
Подключите положительный вывод второго конденсатора 200мкФ к 5 контакту панельки. Позже, второй его вывод будет к подключен к потенциометру «Усиление». Подключите 6 контакт панельки к Vin, 2 контакт к GND и вставьте микросхему в панельку. 
Подключение потенциометра «Усиление»

Громкость или усиление звукового сигнала будет управляться аудио потенциометром 10 кОм с выключателем. Подключите выход усилителя и GND к потенциометру, как показано на фото. Средний контакт это аудио выход, который будет подключен непосредственно к разъему. Также подключить провода к нижнему и левому контактам сзади как на фотографии. Это выключатель, который потом будет подключен к питанию.
Подключение батареи

Подключить черный провод от разъема для батареи к GND Arduino Shield. Один провод от выключателя в потенциометре подключите к красному проводу, а второй провод от выключателя потенциометра к Vin Arduino Shield. Пока не подсоединяйте батарею.
Подключение аудио разъема

Соедините выход усилителя (отрицательный вывод конденсатора подключенного к 5 пятому выводу панельки) с красным проводом присоединенным к аудио разъему раньше. Черный провод подключите к GND Arduino Shield.
Подключение кнопок

Подключите все красные провода от кнопок к 5В и все черные провода к GND Arduino shield. Подключите зеленые провода к аналоговым контактам 0-3 в следующем порядке:
Аналоговый контакт 0 = Прямоугольный
Аналоговый контакт 1 = Треугольный
Аналоговый контакт 2 = Пилообразный
Аналоговый контакт 3 = Синусоидальный
Подключение потенциометров «Частота» и ШИМ

Подключите красный, черный, и зеленый провода к потенциометрам 10кОм и 50кОм, как показано на фотографии. Подключите красный провод к 5В и черный провод к GND Arduino shield. Подключите зеленые провода к аналоговым контактам 4 (ШИМ) и 5 (Частота).
Установка потенциометров

Снимите шайбы и гайки с потенциометров перед установкой в корпус, чтобы позволить им стать вплотную с деревом. После установки потенциометров закрепите их гайками.
Подключение светодиодов

Подсоедините резистор 470 Ом к катоду каждого из четырех светодиодов. Припаяйте черный провод ко второму выводу резистора и красный провод к аноду светодиода. Заизолируйте всё термоусадкой во избежание короткого замыкания. Припаяйте черные провода от всех четырех светодиодов на GND Arduino shield. Припаяйте красные провода к цифровым контактам 8-11.
Установка светодиодов

Приклейте светодиоды в корпусе так, что каждый из них подсвечивал один символ на передней панели :
Цифровой контакт 8 = Прямоугольный
Цифровой контакт 9 = Треугольный
Цифровой контакт 10 = Пилообразный
Цифровой контакт 11 = Синусоидальный
Черный свето рассеиватель

Приклейте свето рассеиватель на вырезы в передней панели с внутренней стороны. Я использовал кусок черного пластикового мешка для мусора.
Программа
Прошейте Arduino кодом в файле function_generator.ino. В коде используются прерывания по таймеру на частоте 100 кГц для отправки новых данных в ЦАП. Остальная часть кода следит за состоянием кнопок и потенциометров. Так как прерывания происходят на высокой частоте, я должен сделать программу обработки прерываний в ISR(TIMER1_COMPA_vect)<> как можно короче. Математические операции с плавающей точкой и с помощью функции sin() занимают слишком много времени. Я рассмотрел с нескольких проектов, и получил это: Для треугольного и пилообразного сигнала я создал переменные sawByte, triByte, sawInc, и triInc. Каждый раз, когда частота меняется, я подсчитываю сумму на которую частота треугольного и пилообразного сигналов должна измениться с частотой 100 кГц:
triInc = 511/period; if (triInc==0) < triInc = 1; >sawInc = 255/period; if (sawInc==0)
То есть все, что должно быть сделано в прерывании, является простой математикой:
case 1://triangle if((period-t) > t); if (t == 0) < triByte = 0; >else < triByte += triInc; >> else < triByte -= triInc; >if (triByte>255) < triByte = 255; >else if (triByte <0)< triByte = 0; >wave = triByte; break; case 2://saw if (t=0) < sawByte=0; >else < sawByte+=sawInc; >wave = sawByte; break;
Для синусоидально сигнала, я написал простой скрипт на Python, который выводит 20000 значений 127+127 sin(х) за один полный цикл:
import math for x in range(0, 20000): print str(int(127+127*math.sin(2*math.pi*x*0.00005)),)+str(","),
Я сохранил этот массив в памяти Arduino под названием sine20000[] и беру из него значения которые необходимо отправить в ЦАП. Это намного быстрее, чем вычислять значения каждый раз.
Последние штрихи

Подключите shield к Arduino. Подключите 9В батарею к разъему. Закрепите эти элементы внутри корпуса. Убедитесь, что USB-порт Arduino доступен снаружи. После запуска вы должны увидеть светящийся индикатор синусоиды.
Установка задней панели и ручек
Просверлите четыре отверстия на задней панели и закрепите её с помощью винтов. Привинтите ручки на потенциометры.

Тестирование
Немного поверните ручку усиление, чтобы включите генератор. Включите штекер в гнездо и подключите к нему осциллограф. Проверяйте каждый сигнал и меняйте его частоту, чтобы убедится, что всё в порядке. Переключитесь на Прямоугольный сигнал и проверьте наличие ШИМ сигнала.
Вы заметите, что прямоугольный сигнал является единственным, который по настоящему регулируется от 1 Гц до 50 кГц. Поскольку частота дискретизации 100 кГц, синусоидальный, треугольный, и пилообразный сигналы становятся немного неузнаваемыми примерно после 25 кГц (4 отсчета за такт-100kHz/25kHz). Пилообразный и треугольный сигналы понижаются примерно 100 Гц, иначе значения triInc и sawInc станут настолько низким, что они округляются до нуля. Синусоидальный сигнал понижается до 1 Гц, но на самом деле до 5Гц, так как Arduino имеет достаточно памяти только для хранения около 20 тыс. значений.
Генератор синусоиды на Arduino или ЦАП R-2R

Предыстория.
У меня есть хороший друг. Тоже «радиолюбитель», самоучка и весьма энергичный молодой человек. Так вот. Попался ему как-то в руки сгоревший «синим пламенем» источник бесперебойного питания (ИБП) – не лучший образец продукции «поднебесной». Починить его оказалось неподъемной задачей. Однако, трансформатор оказался одним из «живучих» элементов, и Дима задумал изготовить небольшой преобразователь 12В в 220В для какого-то применения (в купе с автомобильным аккумулятором) на дачном участке. Немного погуглив и собрав кучу распечаток из Интернета, он обратился ко мне (зная мои поделки на Ардуино) со странным вопросом: «А «красивую» синусоиду твоя Ардуина может генерить?»
А вот тут и начинается текст по делу
Итак. Синусоида. Значит из цифр (которыми оперирует Ардуинка) нам нужно получить аналоговый сигнал… А значит нам нужен Цифро-Аналоговый Преобразователь (или ЦАП, или DAC -по ненашему).
Меня это не сильно испугало. Я уже сталкивался с ЦАП-ми, а конкретнее в детстве паял вариант R-2R. Те, кто постарше наверняка помнят такие чудесные поделки, как Covox ( http://ru.wikipedia.org/wiki/Covox ). В те годы (примерно 90-ые), мне только доводилось мечтать о звуковой карте, а вот вышеупомянутое устройство, да еще сделанное своими руками – доставило столько приятных минут
Итак, сказано – сделано!
Ну, будучи до конца честным, признаюсь. Я для начала поискал готовые решения для Ардуино в просторах Интернета. (Вот один из вариантов, с применением ШИМ-сигнала: http://electronics.stackexchange.com/questions/41738/possible-to-output-sinusoidal-signal-with-an-arduino. Не понравился.)

Итак, нам нужено изготовить простейший цифро-аналоговый преобразователь: R-2R.
Вот буржуйское описание: http://en.wikipedia.org/wiki/Resistor_ladder
(B7..B0 — это биты, B7 — старший, B0 — младший).
Своё название (R-2R) данный ЦАП получил из-за номиналов применяемых в нём резисторов с сопротивлениями R и 2*R. Сопротивления по идее могут быть любыми (1k-2k; 10k-20k и т.д). Однако, я чаще всего встречал варианты с номиналами 1k и 2k.
Как же эта штука работает?
Каждый вход ЦАПа вносит свою лепту в выходной сигнал пропорционально своей «значимости». Т.е. левый вход оказывает самое большое влияние на выходной сигнал (половина опорного напряжения), следующий за ним ¼ , следующий – 1/8 и т.д. Ну а самый последний (правый) вход изменяет выходной сигнал на ничтожные милливольты. Подставляя значение битов на входе ЦАП выходное напряжение можно рассчитать так:
Uвых=Uпит * (B7 * 1/2 + B6 * 1/4 + B5 *1/8+ B4*1/16+B3*1/32+B2*1/64+B1*1/128+B0*1/256).
Если выставить на вход ЦАП-а значение 255 (бинарное 11111111), то получаем самый высокий выходной сигнал. Если же 00000000 — ноль.
Uпит – напряжение питания микроконтроллера. Таким образом, наш восьмибитный ЦАП способен выдать 256 различных напряжений с шагом около 20 милливольт, при опорном напряжении 5 Вольт.
Желательно чтобы ЦАП (8-ми разрядный, как у нас) был подключен к целому порту. Тогда выводить любое значение в ЦАП — будет очень просто:
PORTD = 215;

Итак, прикидываем на макетке:
Проверяем работоспособность, засылая в порт различные значения. Мультиметром замеряем напряжение на выходе — все очень хорошо. Можем двигаться дальше. С «железной» частью вроде как разобрались.
Теперь математическая составляющая.
Вооружившись школьным учебником алгебры (шучу… шучу, конечно же Википедия!) вспоминаем, что такое Синусоида: http://ru.wikipedia.org/wiki/Синусоида
Адаптируем к нашим условиям. ЦАП может выдавать значения от 0 до 255. Причем, за нулевое значение (мы будем оперировать только целыми положительными числами) примем 127. Длительность волны примем 255 шажков (опять же для удобства). Т.е., для одного периода значение функции поменяется 255 раз. Естественно, чем больше «шажков» мы уместим в этот период, тем точнее получим синусоиду.
Синим цветом я постарался обозначить значения напряжения, получаемые на выходе ЦАП, при «контрольных» значениях точек на оси Х.
Общая формула синусоиды:
Y=a+b*SIN(c*X)
Итак, наша синусоида стартует со значением 127 (для ЦАП) и заканчивается этим значением. Для этого, вводим значение смещения по оси У а=127. a характеризует сдвиг графика по оси Oy. Чем больше a, тем выше поднимается график.
Значение синуса может меняться от -1 до 1 (Кто бы мог подумать. ). Чтобы растянуть график по вертикали, вводим второе значение b, характеризующее растяжение графика по оси Oy. Чем больше увеличивается b, тем сильнее возрастает амплитуда колебаний; Ну, тут тоже понятно, что при максимальном значении в (254-127) b=127
с характеризует растяжение графика по оси Ox.
Длина периода =2*Pi. Мы условились, что этот период мы делим на 255 «шагов». Т.е., 255-ый шаг должен иметь значение 2*Pi. Для нашего случая С=2*Pi*(1/255) или 2*Pi*0.0392 или Pi*0.007843
Окончательно получаем следующую формулу расчета: Y=127+127*SIN(Pi*X*0.007843).
(Желающие получить БОЛЕЕ точные результаты, могут использовать, допустим 512 шажков. Только нужно пересчитать константу).
Давайте проверим нашу формулу на «ключевых» значениях X:
0 (0) = 127
64 (Pi/2) =253
128(Pi) =125
192 (3*Pi/2) =0
255 (2Pi) =126
Весьма правдоподобно. Итак далее, тут можно поступить двумя способами: высчитывать значение по ходу дела – способ НАВЕРНЯКА не самый быстрый, а можно заранее рассчитать эти значения и брать их из таблицы. Я предпочел второй способ.
Программист из меня не важный (Бейсик – в детстве, Паскаль – в школе, ФОРТРАН – в институте), поэтому я не стал тратить время на поиски того же Борланд паскаля или изучение Питона, «напрягом» знакомого программиста… Как впрочем и на калькуляторе высчитывать 255 значений мне показалось «времярасточительным» занятием. НО у меня же есть Ардуинка! (И я ОЧЕНЬ стараюсь использовать ее по полной программе.). Вот ее и заставим произвести нужные мне расчеты.
/* Расчет таблицы для значений синусоиды, с выводов в монитор ком-порта */ void setup() < Serial.begin(9600); >void loop() < for (int i=0;i<256;i++) < Serial.print(byte(127+(127*sin(PI*i*0.007843)))); Serial.print(", "); >delay(10000); // Задержка, чтобы успеть скопировать :) >

Вот наша таблица значений :
127, 130, 133, 136, 139, 142, 145, 148, 151, 154, 157, 161, 164, 166, 169, 172, 175, 178, 181, 184, 187, 189, 192, 195, 197, 200, 202, 205, 207, 210, 212, 214, 217, 219, 221, 223, 225, 227, 229, 231, 232, 234, 236, 237, 239, 240, 242, 243, 244, 245, 246, 247, 248, 249, 250, 251, 251, 252, 252, 253, 253, 253, 253, 253, 253, 253, 253, 253, 253, 252, 252, 251, 251, 250, 249, 249, 248, 247, 246, 245, 243, 242, 241, 239, 238, 236, 235, 233, 231, 230, 228, 226, 224, 222, 220, 218, 215, 213, 211, 209, 206, 204, 201, 199, 196, 193, 191, 188, 185, 182, 180, 177, 174, 171, 168, 165, 162, 159, 156, 153, 150, 147, 144, 141, 137, 134, 131, 128, 125, 122, 119, 116, 112, 109, 106, 103, 100, 97, 94, 91, 88, 85, 82, 79, 76, 73, 71, 68, 65, 62, 60, 57, 54, 52, 49, 47, 44, 42, 40, 38, 35, 33, 31, 29, 27, 25, 23, 22, 20, 18, 17, 15, 14, 12, 11, 10, 8, 7, 6, 5, 4, 4, 3, 2, 2, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 13, 14, 16, 17, 19, 21, 22, 24, 26, 28, 30, 32, 34, 36, 39, 41, 43, 46, 48, 51, 53, 56, 58, 61, 64, 66, 69, 72, 75, 78, 81, 84, 86, 89, 92, 96, 99, 102, 105, 108, 111, 114, 117, 120, 123, 126,
Набрасываем скетчик для вывода значений таблицы в ЦАП:
/* Ghost D. 2013 DAC R/2R Это генератор синусоиды с частотой 50Гц */ unsigned char sin_tab [256] = < 127,130,133,136,139,142,145,148,151,154,157,160,164,166,169,172,175,178,181,184,187,189,192,195,197,200,202,205,207,210,212,214,217,219,221,223,225,227,229,231,232,234,236,237,239,240,242,243,244,245,246,247,248,249, 250,251,251,252,252,253,253,253,253,253,254,253,253,253,253,252,252,251,251,250,249,249,248,247,246,245,243,242,241,239,238,236,235,233,231,230, 228,226,224,222,220,218,215,213,211,209,206,204,201,199,196,193,191,188,185,182,180,177,174,171,168,165,162,159,156,153,150,147,144,141,137,134,131,128,125,122,119,116,112,109,106,103,100,97,94,91,88,85,82,79,76, 73,71,68,65,62,60,57,54,52,49,47,44,42,40,38,35,33,31,29,27,25,23,22,20,18,17,15,14,12,11,10,8,7,6,5,4,4,3,2,2,1,1,0,0,0,0,0,0,0,0,0,0,1,1,2,2,3,4,5,6,7,8,9,10,11,13,14,16,17,19,21,22,24,26,28,30,32,34,36,39,41,43,46,48,51,53,56,58,61,64,66,69,72,75,78,81,84,87,89,93,96,99,102,105,108,111,114,117,120,123,127>; void setup() < DDRD =0xFF;>void loop() < for (int i=0;i<256;i++) < PORTD=sin_tab[i]; delayMicroseconds(75); >>

Вроде как и все. Но, как говорят французы, «аппетит приходит во время еды», а именно, оказывается нужно две синусоиды.
А, была-не была. Тем более, столько уже проделано. Макетим.
«Допиливаем» скетч, сделаем небольшое смещение синусоид offset 128. (Кстати, огромная благодарность Юре, за идею «склейки» пинов для получения «полного» порта).
/* DAC R/2R 50Hz Ghost D. 2013 Это генератор синусоид с частотой 50Гц */ unsigned char sin_tab [256] = < 127,130,133,136,139,142,145,148,151,154,157,160,164,166,169,172,175,178,181,184,187,189,192,195, 197,200,202,205, 207,210,212,214,217,219,221,223,225,227,229,231,232,234,236,237,239,240,242,243, 244,245,246,247,248,249, 250,251, 251,252,252,253,253,253,253,253,254,253,253,253,253,252,252,251, 251,250,249,249,248,247,246,245,243,242,241,239, 238,236,235,233,231,230, 228,226,224,222,220,218, 215,213,211,209,206,204,201,199,196,193,191,188,185,182,180,177, 174,171,168,165,162,159,156,153, 150,147,144,141,137,134,131,128,125,122,119,116,112,109,106,103,100,97,94,91,88, 85,82,79,76, 73,71,68,65,62,60,57,54,52,49,47,44,42,40,38,35,33,31,29,27,25,23,22,20,18,17,15,14, 12,11,10,8,7,6,5, 4,4,3,2,2,1,1,0,0,0,0,0,0,0,0,0,0,1,1,2,2,3,4,5,6,7,8,9,10,11,13,14,16,17,19,21,22,24, 26,28,30,32,34,36,39,41,43,46, 48,51,53,56,58,61,64,66,69,72,75,78,81,84,87,89,93,96, 99,102,105,108,111,114,117,120,123,127>; void setup() < DDRD = 0xFF; DDRC |= 0b00111111; // PC0-PC5 DDRB |= 0b00011000; // PB3-PB4 >void pp(unsigned char i) < // (PORTC & 0b11000000) - Обнуляем только используемые биты // i >> 2 - сдвигаем (отбрасываем первые 2 бита, которые будем выводить в порт Б // (PORTC & 0b11000000) | (i >> 2) выставляем только оставшиеся 5 бит в порту С PORTC = (PORTC & 0b11000000) | (i >> 2); // (PORTB & 0b11100111) - обнуляем только три используемых бита // (i & 0b00000011) - обнуляем неиспользуемые биты // (PORTB & 0b11100111) | (i & 0b00000011) - выставляем нужные биты в порту Б PORTB = (PORTB & 0b11100111) | (i & 0b00000011); > #define offset 128 void loop() < for (int i = 0, j = i + offset; i < 256; i++, j++) < PORTD = sin_tab[i]; pp(sin_tab[j]); if (j == 255) j = 0; delayMicroseconds(75); >>

Делаем контрольный замер, подключив выходы ЦАП-ов к осциллографу (красиво совмещаем выдаваемые Ардуинкой синусоиды):
Красота! То, что и желали получить. Все, мой приятель остался очень доволен. Я отдал ему прошитую Atmeg-у для дальнейшего применения.
Про дальнейшую судьбу его устройства — это отдельная история, не входящая в рамки данного опуса.
P.S. Вот фото того устройства, ради которого все это и было проделано
Генератор сигналов прямоугольной и синусоидальной формы на Arduino
Каждый инженер, увлекающийся электроникой, на определенном этапе своей деятельности мечтает о создании своей мини лаборатории. Мультиметр, осциллограф, генератор сигналов специальной формы, источник питания, трансформатор – вот лишь минимальный обязательный набор для подобной лаборатории. Конечно, сейчас все это можно купить, но чтобы сэкономить свои деньги, часть из этих устройств можно сделать самостоятельно на основе платы Arduino. Например, генератор сигналов или осциллограф.

В этой статье мы рассмотрим как на основе платы Arduino достаточно просто сконструировать генератор сигналов прямоугольной и синусоидальной формы. При формировании сигналов прямоугольной формы данный генератор может формировать прямоугольную волну с перепадами уровней 5V/0V с частотой от 1 Гц до 2 МГц. Частотой формируемого сигнала можно будет управлять с помощью инкрементального энкодера. Коэффициент заполнения (цикл занятости) данного сигнала будет равен 50%, но его можно изменить, внеся соответствующие изменения в программу. Рассматриваемый нами генератор не является промышленным устройством и его не рекомендуется использовать на серьезном производстве, но для домашних условий использования он вполне подойдет.
Также на нашем сайте вы можете посмотреть проекты более «продвинутых» генераторов для формирования сигналов прямоугольной и синусоидальной формы:
- генератор сигналов на Arduino и DDS модуле AD9833;
- генератор перестраиваемой частоты 10 кГц – 225 МГц на Arduino и модуле Si5351.
Если же вам нужно исключительно простое решение для формирования сигналов прямоугольной формы с частотой до 1 МГц с помощью платы Arduino, то рекомендуем этот проект.
Необходимые компоненты
- Плата Arduino Nano (купить на AliExpress).
- Алфавитно-цифровой ЖК дисплей 16х2 (купить на AliExpress).
- Инкрементальный энкодер, угловой кодер (Rotary Encoder) (купить на AliExpress).
- Резисторы 5,6 кОм и 10 кОм (купить на AliExpress).
- Конденсатор 0,1 мкФ (купить на AliExpress).
- Перфорированная плата.
- Набор для пайки.
Работа схемы
Схема генератора сигналов на основе платы Arduino представлена на следующем рисунке.

Плата Arduino Nano управляет всеми процессами в схеме. ЖК дисплей используется для отображения частоты формируемого сигнала, а с помощью углового кодера производится установка частоты сигнала. Также на нашем сайте вы можете прочитать статью о подключении инкрементального энкодера к плате Arduino.
Схема запитывается от USB кабеля Arduino. Необходимые соединения в схеме представлены в следующей таблице.
| Контакт платы Arduino | Куда подключен |
| D14 | контакт RS ЖК дисплея |
| D15 | контакт RN ЖК дисплея |
| D4 | контакт D4 ЖК дисплея |
| D3 | контакт D5 ЖК дисплея |
| D6 | контакт D6 ЖК дисплея |
| D7 | контакт D7 ЖК дисплея |
| D10 | to Rotary Encoder 2 |
| D11 | to Rotary Encoder 3 |
| D12 | to Rotary Encoder 4 |
| D9 | выход прямоугольного сигнала |
| D2 | контакт D9 платы Arduino |
| D5 | выход SPWM сигнала |
В схеме мы будем формировать прямоугольную волну (сигнал прямоугольной формы) на контакте D9 платы Arduino. Его частоту мы будем регулировать с помощью углового кодера. Для формирования синусоидального сигнала мы будем формировать SPWM сигнал (синусоидальный ШИМ (широтно-импульсной модуляции) сигнал) на контакте D5, его частота будет зависеть от частоты сигнала прямоугольной формы, которая будет подаваться на контакт D2 и будет действовать как прерывание и затем мы с помощью процедуры обработки (обслуживания) прерывания будем управлять частотой синусоидального сигнала.
Вы можете собрать схему проекта на макетной или даже на печатной плате, но мы решили спаять ее на перфорированной плате, в результате у нас получилась конструкция, показанная на следующих рисунках:

Формирование прямоугольного сигнала с изменяемой частотой
Если вы знакомы с Arduino, то вы должны знать что плата Arduino может достаточно просто формировать ШИМ сигнал (с помощью функции analogwrite) на ряде своих контактов. Но с помощью этой функции можно управлять только коэффициентом заполнения (скважностью) ШИМ сигнала, но нельзя управлять его частотой – а это как раз и нужно для нашего генератора сигналов. Управление частотой сигнала прямоугольной формы можно осуществить используя таймеры платы Arduino и непосредственно переключая состояние контактов на их основе. Помочь нам в этом может библиотека Arduino PWM Frequency Library (библиотека управления частотой ШИМ сигнала), более подробно работу с ней мы рассмотрим далее в статье.

Но в использовании этой библиотеки есть ряд слабых сторон. Дело в том, что данная библиотека изменяет настройки по умолчанию Таймера 1 (Timer 1) и Таймера 2 (Timer 2) платы Arduino. В связи с этим вы уже не сможете, к примеру, использовать библиотеку для управления серводвигателем или другие библиотеки, задействующие эти таймеры платы Arduino. Также функция analogwrite на контактах 9,10,11 & 13 использует Timer 1 и Timer 2, следовательно, вы уже не сможете формировать SPWM сигнал (синусоидальный ШИМ сигнал) на этих контактах.
Но преимуществом этой библиотеки является то, что она не мешает работа Таймера 0 (Timer 0) платы Arduino, который в нашем случае является более важным чем Timer 1 и Timer 2 потому что в этом случае вы можете без проблем использовать функцию задержки (delay) и функцию millis(). Также контакты 5 и 6 управляются Таймером 0, поэтому мы без проблем сможем использовать на этих контактах функцию analogwrite или осуществлять управление сервомотором.
Формирование синусоидальной волны (колебания) с помощью Arduino
Мы знаем, что микроконтроллеры являются цифровыми устройствами, поэтому они не могут формировать синусоидальную волну в «чистом» виде. Но есть два способа формирования синусоидальной волны с помощью микроконтроллера: первый заключается в использовании ЦАП (цифро-аналогового преобразователя), а второй — в использовании синусоидального ШИМ сигнала (SPWM). К сожалению, в платах Arduino (за исключением платы Arduino Due) нет встроенного ЦАПа для формирования синусоидальной волны. Конечно, можно было бы использовать внешний ЦАП, но мы решили не усложнять таким образом схему проекта и использовать метод формирования синусоидального ШИМ сигнала с дальнейшим преобразованием его в синусоидальный сигнал (волну).
Что такое SPWM сигнал
SPWM расшифровывается как Sinusoidal Pulse Width Modulation и переводится как синусоидальная широтно-импульсная модуляция (синусоидальная ШИМ). Этот сигнал в определенной степени похож на обычный ШИМ сигнал, но в нем коэффициент заполнения контролируется таким образом чтобы получить среднее напряжение похожее на синусоидальную волну. Например, при коэффициенте заполнения (скважности) 100% среднее выходное напряжение будет 5V, а при коэффициенте заполнения 25% оно будет всего лишь 1.25V, таким образом, управляя скважностью (коэффициентом заполнения) мы можем получить заранее определенные изменяемые значения среднего напряжения, то есть синусоидальную волну. Этот метод обычно используется в инверторах.
Принцип формирования SPWM сигнала показан на следующем рисунке.

Синим цветом на этом рисунке показан SPWM сигнал. Заметьте, что его скважность (коэффициент заполнения) изменяется от 0% до 100%, а затем снова возвращается в 0%. Представленный график построен для диапазона изменения напряжений от -1.0 до +1.0V, но в нашем случае, поскольку мы используем плату Arduino, масштаб подобного графика будет от 0V до 5V. Мы рассмотрим как в программе для Arduino формировать SPWM сигнал далее в статье.
Преобразование SPWM сигнала в синусоидальную волну
Преобразование SPWM сигнала в синусоидальную волну обычно требует использования схемы H-моста (H-bridge), которая состоит минимум из 4-х переключателей мощности (power switches). Подобные схемы обычно используются в инверторах. Мы не будем в статье подробно рассматривать эти вопросы поскольку нам с помощью нашей синусоидальной волны не нужно запитывать какое-либо устройство, нам всего лишь нужно ее сформировать. К тому же с помощью H-моста невозможно получить чистую синусоидальную волну – для этой цели необходимо использовать фильтр нижних частот (ФНЧ), состоящий из конденсаторов и индуктивностей.

Но мы в целях упрощения проекта для этой цели применили простой RC-фильтр. Если же вы хотите повысить качество формирования синусоидальной волны, то вы можете вместо RC-фильтра применить LC-фильтр. Значение сопротивления резистора в нашем RC-фильтре составляет 620 Ом, а значение емкости конденсатора составляет 10 мкФ (номиналы отличаются от тех, которые приведены в начале статьи в разделе «необходимые компоненты», но я надеюсь в комментариях к данной статье более опытные в этих вопросах специалисты подскажут где же здесь правда – статья переведена с другого сайта и там присутствует эта опечатка, к сожалению). На представленном рисунке желтым цветом показан SPWM сигнал с контакта 5 платы Arduino, а синим цветом — синусоидальный сигнал, полученный после прохождения SPWM сигнала через наш RC-фильтр.
Библиотека для управления частотой ШИМ сигнала в Arduino
Эту библиотеку вы можете скачать по следующей ссылке — Arduino PWM Frequency Library.
По представленной ссылке вы скачаете библиотеку в виде ZIP файла. После извлечения информации из этого ZIP файла вы получите каталог (папку) с именем PWM. Перейдите в папку с библиотеками Arduino IDE (для пользователей операционной системы Windows эта папка будет располагаться по адресу C:\Users\User\Documents\Arduino\libraries) и скопируйте туда эту PWM папку. Возможно, в библиотеках Arduino IDE у вас уже есть папка с именем PWM – в этом случае вам ее необходимо заменить на новую (скачанную) папку.
Объяснение программы для Arduino
Полный код программы приведен в конце статьи, здесь же мы рассмотрим его наиболее важные фрагменты. Перед компиляцией программы не забудьте добавить в библиотеки Arduino указанную библиотеку Arduino PWM Frequency Library, иначе компиляция программы будет выдавать вам ошибку.
Нам необходимо формировать ШИМ сигнал с изменяемой частотой на контакте 9 платы Arduino. Эта частота будет устанавливаться с помощью углового кодера, а ее значение будет отображаться на экране ЖК дисплея. А когда ШИМ сигнал будет формироваться на контакте 9 он также будет создавать прерывание на контакте 2 поскольку мы соединили оба этих контакта. Используя это прерывание мы можем управлять частотой SPWM сигнала, который будет формироваться на контакте 5.
Как обычно вначале программы мы должны подключить используемые библиотеки. Библиотека для работы с ЖК дисплеем встроена в Arduino IDE, а библиотеку для изменения частоты ШИМ сигнала мы только что скачали.