Генератор сигналов на arduino

Генератор сигналов на AD9833

AD9833 – это программируемый генератор сигналов с низким энергопотреблением. Позволяет генерировать сигналы с частотой до 12.5МГц синусоидальной, треугольной и прямоугольной формы. Программирование осуществляется с использованием трехпроводного интерфейса SPI и не составляет труда.

Ниже приведены основные характеристики микросхемы:

  • Цифровое программирование частоты и фазы.
  • Потребляемая мощность 12.65 мВт при напряжении 3 В.
  • Диапазон выходных частот от 0 МГц до 12.5 МГц.
  • Разрешение 28 бит (0.1 Гц при частоте опорного сигнала 25 МГц).
  • Синусоидальные, треугольные и прямоугольные выходные колебания.
  • Напряжение питания от 2.3 В до 5.5 В.
  • Трехпроводной интерфейс SPI.
  • Расширенный температурный диапазон: от –40°C до +105°C.
  • Опция пониженного энергопотребления.

Более подробную информацию вы можете найти в даташите.

В характеристиках также заявлено, что микросхема не требует внешних компонентов, но здесь производитель лукавит: обвязка и источник опорной частоты все же нужны. На Али продаются модули AD9833 с необходимой обвязкой и кварцевым генератором на 25 МГц, как раз с таким модулем я и собираюсь экспериментировать.

Данный модуль имеет следующие выводы:

  • VCC – плюс питания для цифровых и аналоговых цепей генератора.
  • DGND – цифровая земля.
  • SDATA – вход данных интерфейса SPI. Передача осуществляется 16-битными словами.
  • SCLK – вход тактового сигнала SPI. Используется второй режим работы: (CPOL = 1, CPHA = 0).
  • FSYNC – выбор микросхемы. Перед началом передачи данных должен быть установлен в 0, по завершении в 1.
  • AGND – аналоговая земля.
  • OUT – выход генератора.

Попробуем подключить этот модуль к Ардуино и научиться им управлять.

Для начала ознакомимся с его функциональной схемой:AD9833 состоит из следующих основных частей: два регистра выбора частоты, аккумулятор фазы, два регистра выбора фазы и сумматор смещения фазы (вместе эти компоненты составляют генератор с цифровым управлением – NCO), SIN ROM для преобразования информации о фазе в амплитуду и 10-разрядный цифро-аналоговый преобразователь.

Из схемы видно, что данные с интерфейса SPI передаются в управляющий регистр, регистры выбора фазы и частоты. Именно они определяют сигнал на выходе генератора. И программирование генератора сводится к изменению содержимого указанных регистров. Это 16-разрядный регистр, управляющий работой генератора. Подробное описание его битов приведено ниже в таблице.

Схема из даташита также наглядно демонстрирует их назначение:

БитНазваниеНазначение
15, 14 DB15, DB14 Чтобы AD9833 понял, что принятое по SPI 16-битное слово содержит новое значение для управляющего регистра, два старших бита в слове должны быть установлены в 0.
13 B28 Регистры частоты AD9833 имеют разрядность 28 бит, поэтому для изменения их содержимого требуется передача двух 16-битных слов. Однако в некоторых случаях требуется изменить только старшую или младшую часть регистра частоты. Здесь и используется данный признак: B28 = 1 говорит о том, что необходимо обновить регистр частоты целиком и его новое значение будет передано двумя последовательными записями. Первая запись содержит 14 младших бит, вторая 14 старших бит. Первые два бита в обеих записях определяют регистр частоты, в который будет записано передаваемое значение и должны быть одинаковыми. Обновление регистра частоты происходит после получения полного слова, поэтому запись промежуточного значения в регистр исключена. B28 = 0 позволяет обновить отдельно старшую или младшую часть регистра. Какая именно часть будет изменена определяется управляющим битом HLB.
12 HLB Бит HLB определяет, какая из частей регистра частоты (младшая или старшая) будет перезаписана. Используется при B28 = 0. При B28 = 1 значение этого бита игнорируется. HLB = 1 позволяет обновить старшие 14 бит регистра частоты; HLB = 0 позволяет обновить младшие 14 бит регистра частоты.
11 FSELECT Бит FSELECT определяет, какой из регистров используется в аккумуляторе фазы – FREQ0 или FREQ1.
10 PSELECT Бит PSELECT определяет, данные какого из регистров PHASE0 или PHASE1 добавляются к выходу аккумулятора фазы.
9 Зарезервирован Данный бит зарезервирован и должен быть установлен в 0.
8 RESET RESET = 1 сбрасывает внутренние регистры генератора в 0. Сброс не затрагивает регистры управления, частоты и фазы.
7 SLEEP1 При SLEEP1 = 1 запрещается внутреннее тактирование, приостанавливается работа NCO и выход генератора остается в своем текущем состоянии. При SLEEP1 = 0 тактирование разрешено.
6 SLEEP12 При SLEEP12 = 1 отключается внутренний ЦАП. Это может быть полезно для генерации прямоугольных импульсов, при которой не требуется выполнение цифро-аналоговых преобразований. При SLEEP12 = 0 внутренний ЦАП активен.
5 OPBITEN Данный бит вместе с битом MODE управляют выходом генератора. При OPBITEN = 1 внутренний ЦАП отключается от выхода VOUT и для генерации выходного сигнала используется значение старшего значащего бита с входа ЦАП, что позволяет получить на выходе генератора прямоугольные импульсы.
4 Зарезервирован Данный бит зарезервирован и должен быть установлен в 0.
3 DIV2 Используется в паре со значением OPBITEN = 1. При DIV2 = 1 значение старшего значащего бита данных с входа ЦАП подается напрямую на выход VOUT. DIV2 = 0 позволяет задействовать делитель частоты и уменьшить частоту выходного сигнала вдвое. При OPBITEN = 0 значение данного бита игнорируется.
2 Зарезервирован Данный бит зарезервирован и должен быть установлен в 0.
1 MODE Данный бит вместе с битом OPBITEN управляют выходом генератора. При OPBITEN = 1 бит MODE должен быть установлен в 0. Значение MODE = 0 позволяет получить на выходе генератора синусоидальный сигнал. При MODE = 1 на выходе будет треугольный сигнал.
Зарезервирован Данный бит зарезервирован и должен быть установлен в 0.

И для лучшего понимания назначения битов OPBITEN, MODE и DIV2 я приведу таблицу с их допустимыми комбинациями и формой результирующих сигналов на выходе:

OPBITENMODEDIV2Сигнал на выходе VOUT
X Синусоидальный
1 X Треугольный
1 Прямоугольный с частотой F/2
1 1 Прямоугольный с частотой F
1 1 X Зарезервировано

Генератор AD9833 имеет 2 регистра частоты и 2 регистра фазы разрядностью 28 бит и 12 бит соответственно. 

Выбор активного регистра частоты осуществляется установкой управляющего бита FSELECT: при FSELECT = 0 активным является FREQ0; при FSELECT = 1 активен регистр FREQ1. Результирующая частота на выходе генератора определяется следующим образом:

(FMCLK / 228) * FREQREG,

где FMCLK – это опорная частота, FREQREG – значение, загруженное в активный регистр частоты. Таким образом, если мы хотим получить на выходе генератора сигнал с частотой 400Гц при опорной частоте 25МГц, в активный регистр  должно быть загружено значение:

FREQREG = FOUT*228 / FMCLK = 400Гц * 228 / 25МГц ≈ 4295

Для того чтобы загрузить значение FREQREG в регистр частоты необходимо, старшие биты передаваемого по SPI значения установить в 01 для загрузки в FREQ0 или 10 для загрузки в FREQ1.

Напомню, что общение с AD9833 осуществляется по SPI 16-битными словами.

Фаза выходного сигнала определяется следующим образом:(2π / 212) * PHASEREG соответственно, значение для регистра фазы вычисляется по формуле:

PHASEREG = PHASE*212 / 2π

В приведенных формулах PHASEREG – это значение активного регистра фазы. Выбор активного регистра осуществляется установкой управляющего бита PSELECT: при PSELECT = 0 активным является PHASE0; при PSELECT = 1 активен регистр PHASE1.

При записи нового значения в регистр фазы старшие биты должны быть установлены в 11, а выбор регистра, в который должно быть записано значение, осуществляется установкой бита 13: при нулевом его значении будет обновлен регистр PHASE0; при установке указанного бита в 1 будет обновлен регистр PHASE1. 12й бит не используется, а биты с 0 по 11 содержат значение для регистра фазы.

Разрядность регистра частоты в 28 бит при опорной частоте 25МГц обеспечивает шаг 0.1Гц для установки частоты сигнала на выходе. А 12-битный регистр фазы обеспечивает разрешение 2π/4096.

Тестовая программа для AD9833 на Ардуино

Теперь мы можем написать первую программу для AD9833. Схема подключения модуля AD9833 к Ардуино и скетч приведены ниже.

С подключением все просто: общение с модулем происходит по интерфейсу SPI, для которого на Ардуино отведены следующие пины: D10 – SS (Slave Select – выбор ведомого), к нему подключаем вывод FSYNC модуля. D11 – MOSI (Master Out Slave In – выход ведущего, вход ведомого), к нему подключаем вывод SDATA. D13 – SCK (Serial Clock – Тактовый сигнал), к нему подключаем вывод SCLK. void setup() { SPI.begin(); WriteAD9833(0x2100); WriteAD9833(0x50C7); WriteAD9833(0x4000); WriteAD9833(0xC000); WriteAD9833(0x2000); } void WriteAD9833(uint16_t Data){ SPI.beginTransaction(SPISettings(SPI_CLOCK_DIV2, MSBFIRST, SPI_MODE2)); digitalWrite(SS, LOW); delayMicroseconds(1); SPI.transfer16(Data); digitalWrite(SS, HIGH); SPI.endTransaction();
} void loop() { WriteAD9833(0x2000); delay(5000); WriteAD9833(0x2002); delay(5000); WriteAD9833(0x2020); delay(5000); WriteAD9833(0x2028); delay(5000); }В данном скетче выполняются следующие действия:

  • При первом вызове функции WriteAD9833 производится установка управляющего регистра: бит RESET устанавливается в 1 для выполнения сброса; бит DB28 устанавливается в 1 для перезаписи всего содержимого регистра частоты; биты FSELECT и PSELECT содержат 0, поэтому для генерации выходного сигнала будут использоваться регистры FREQ0 и PHASE0.
  • Следующие два вызова передают значение 4295 в регистр частоты FREQ0. Данное значение умещается в 14 младших разрядах, поэтому в старшие разряды регистра записываем нули.
  • Сдвиг по фазе не требуется – запишем в регистр PHASE0 значение 0
  • Последним вызовом WriteAD9833 в процедуре setup снимаем бит RESET, разрешая тем самым работу генератора. Результирующий сигнал поступает на вывод VOUT.
  • Следующие вызовы WriteAD9833 в функции loop обновляют содержимое управляющего регистра, перебирая комбинации битов MODE, OPBITEN и DIV2 для генерации сигнала синусоидальной, треугольной и прямоугольной форм.

Вот как выглядит выходной сигнал генератора в виртуальном осциллографе:

Синусоидальный сигнал (биты MODE и OPBITEN сброшены в 0)

Треугольный сигнал (MODE = 1, OPBITEN = 0)

Прямоугольный сигнал (OPBITEN = 1, MODE = 0, DIV2 = 1)

Прямоугольный сигнал (OPBITEN = 1, MODE = 0, DIV2 = 0)

Обратите внимание: при генерации синусоидальных и треугольных импульсов, когда сигнал снимается с выхода ЦАП, его амплитуда изменяется в диапазоне -300мВ…300мВ. При генерации импульсов прямоугольной формы мы имеем дело с обычным цифровым сигналом с соответствующими уровнями напряжения. Так в последних двух осциллограммах логической единице соответствует напряжение ~4,5В.Разобравшись с управлением AD9833 можно приступать к созданию генератора с интерфейсом управления и индикацией. Для этого добавим в нашу схему энкодер вращения и жидкокристаллический дисплей:

Ранее я писал о том, как можно сделать меню на Ардуино с энкодером вращения. И сейчас я взял такое меню за основу скетча, добавив в него функционал для работы с AD9833. Скачать скетч можно по ссылке. При включении питания AD9833 настраивается на генерацию синусоидального сигнала частотой 100Гц, соответствующая информация отображается на дисплее. Вращая ручку энкодера можно изменять его частоту, а при нажатии вызывается меню. В меню доступны следующие опции:

  • Установка частоты (можно задать произвольное значение от 1 до 12,5МГц).
  • Установка фазы (0 – 360°).
  • Выбор формы сигнала.
  • Выбор значения, на которое изменяется частота при вращении ручки энкодера.

Остается только поместить все компоненты в подходящий корпус и получится законченное устройство. Результат работы в следующем ролике:

Источник: https://tsibrov.blogspot.com/2018/06/ad9833.html

Генератор синусоиды на Arduino или ЦАП R-2R

Предыстория.
У меня есть хороший друг. Тоже «радиолюбитель», самоучка и весьма энергичный молодой человек. Так вот. Попался ему как-то в руки сгоревший «синим пламенем» источник бесперебойного питания (ИБП) – не лучший образец продукции «поднебесной». Починить его оказалось неподъемной задачей.

Однако, трансформатор оказался одним из «живучих» элементов, и Дима задумал изготовить небольшой преобразователь 12В в 220В для какого-то применения (в купе с автомобильным аккумулятором) на дачном участке.

Немного погуглив и собрав кучу распечаток из Интернета, он обратился ко мне (зная мои поделки на Ардуино) со странным вопросом: «А «красивую» синусоиду твоя Ардуина может генерить?»
А вот тут и начинается текст по делу 🙂 Итак. Синусоида.

Значит из цифр (которыми оперирует Ардуинка) нам нужно получить аналоговый сигнал… А значит нам нужен Цифро-Аналоговый Преобразователь (или ЦАП, или DAC -по ненашему).

Меня это не сильно испугало. Я уже сталкивался с ЦАП-ми, а конкретнее в детстве паял вариант R-2R.

Те, кто постарше наверняка помнят такие чудесные поделки, как Covox ( ). В те годы (примерно 90-ые), мне только доводилось мечтать о звуковой карте, а вот вышеупомянутое устройство, да еще сделанное своими руками – доставило столько приятных минут 🙂

Итак, сказано – сделано!

Ну, будучи до конца честным, признаюсь. Я для начала поискал готовые решения для Ардуино в просторах Интернета. (Вот один из вариантов, с применением ШИМ-сигнала: Не понравился.)

Итак, нам нужено изготовить простейший цифро-аналоговый преобразователь: R-2R.

Вот буржуйское описание:

(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;

Итак, прикидываем на макетке:Проверяем работоспособность, засылая в порт различные значения. Мультиметром замеряем напряжение на выходе — все очень хорошо. Можем двигаться дальше. С «железной» частью вроде как разобрались. Теперь математическая составляющая.

Вооружившись школьным учебником алгебры (шучу… шучу, конечно же Википедия!) вспоминаем, что такое Синусоида:

Адаптируем к нашим условиям. ЦАП может выдавать значения от 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 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. Вот фото того устройства, ради которого все это и было проделано 🙂

Источник: http://robocraft.ru/blog/2911.html

Vanyamba uses Linux – Arduino. Генератор сигналов

При подключении к Arduino динамика с регулятором громкости, в качестве микропрограммы использовался скетч toneMelody из меню Examples->Digital, проигрывающий мелодию после сброса или включения Arduino.

Если посмотреть, как устроен код этого скетча, то выясняется, что ноты воспроизводятся с помощью функции tone(), которая схожа с функцией delay() тем, что пока выполняется функция, программа ждёт окончания её выполнения.

Но что, если я захочу например подключить к Arduino несколько кнопок и играть разные ноты, или подключить потенциометр и менять тональность звучащей ноты? Ведь нота должна звучать пока нажата кнопка, то есть мне нужна возможность и воспроизводить звук, и опрашивать порты ввода-вывода одновременно.

Такая возможность у микроконтроллера Arduino есть, но для это мне потребуется освоить прерывания и программирование таймеров.

Методы генерации сигналов с помощью таймеров отличаются в разных режимах таймеров.

В нормальном режиме таймер-счётчик последовательно увеличивает значение регистра TCNTn (где n – номер счётчика) на каждом такте генератора, от которого таймер-счётчик приводится в действие.

Когда значение регистра TCNTn достигает максимального, то на следующем такте происходит переполнение счётчика, регистр обнуляется и вызывается прерывание по переполнению TIMERn_OVF_vect, если вызов этого прерывания включен.

Соответственно, метод генерирования сигналов в нормальном режиме применяется один из двух.

В первом случае разрешается переключение состояния вывода OCRnx (где x – это A или B). Это удобно, поскольку не требует тратить на генерацию сигнала процессорного времени, но таким образом можно получить только ограниченное число частот – по количеству значений предделителя. То есть пять частот для таймеров 0 и 1, и 7 частот для таймера 2.

Поэтому чаще в нормальном режиме применяется второй метод, который состоит в том, по прерыванию в регистр TCNTn записывается новое значение счётчика, и таким образом количество частот, которые могут быть сгенерированы, увеличивается примерно в 250 раз для 8-битных таймеров и в 65535 раз для 16-битного таймера.

Тем не менее, второй способ более похож на программную реализацию режима CTC (очистки при совпадении), поэтому этот метод применяется в основном для использования ШИМ в качестве цифро-аналогового преобразователя.

Итак, я собираюсь генерировать сигнал аудио-частоты, то есть в диапазоне от 20 Гц до 22 кГц. Для этого я использую вывод Digital с номером 11, который соответствует выводу OCR2A, то есть выводу Compare Match A таймера-счётчика 2.

Для установки таймера-счётчика 2 в режим CTC я должен установить биты WGM в значение 2 (бинарное 010). Чтобы включить режим переключения состояния вывода OCR2A при совпадении (Toggle on Compare Match) мне нужно установить биты COM2A в значение 1 (бинарное 01).

Поскольку в этом режиме при совпадении значений регистров OCR2A и TCNT2 происходит обнуление регистра TCNT2, то чем меньше значение регистра OCR2A, тем выше частота сигнала, а чем больше значение регистра OCR2A, тем частота ниже. Минимальная частота сигнала, которую я смогу получить с предделителем /1024, равна согласно формуле из документации, равна

16 МГц / (2 * 1024 * 256) = 30 Гц,

а максимальная –

16 МГц / (2 * 1024 * 1) = 7812 Гц,

что вполне укладывается в диапазон аудио-частот.

Для выбора в качестве источника тактовых импульсов для таймера-счётчика 2 предделителя /1024, я должен установить биты CS2 в значение 7 (бинарное 111).

Чтобы изменение состояния вывода OCR2A отображалось на состоянии вывода (пина) микроконтроллера, мне нужно перевести пин в режим вывода (OUTPUT).

Таким образом, я создаю новый скетч и добавляю следующий код:

#define R3_PIN (A0)#define SPEAKER_PIN (11)#define T2_WGM (0b010)#define T2_COMA (0b01)#define T2_CS (0b111)
void setup() { TCCR2A = (T2_COMA >5); // Спад ноты (decay) while(waitForInterrupt); // Ожидание прерывания OCR2A = (out >> 8) & 0xFF; // Вывод текущего значения волны на вывод ШИМ OC2A waitForInterrupt = true; // Установка флага ожидания прерывания }}
unsigned int getNote(char key) // Преобразование клавиши в приращение счётчика{ // генератора волны for(unsigned char n = 0; n < 14; ++n) { if (keys[n] == key) { return notes[n]; } } return notes[0]; // Если клавиша не определена, проиграть паузу}

Скачать скетч можно, кликнув по ссылке.

Скомпилировав и загрузив скетч в память Arduino, откройте окно Serial Monitor и установите скорость обмена, заданную в скетче – 57600 бод.

Чтобы проиграть гамму, отправьте в Arduino строку символов zxcvbnm, (символ запятая соответствует ноте до второй октавы). Соответственно, рок-н-ролл можно сыграть, отправив строку zcb.zcb.zcbnjnbc.

(точка в данном скетче неопределённый символ, поэтому вместо неё будет проиграна пауза).

Прослушать демо можно, кликнув по ссылке.

Записывая демо, я обратил внимание, что громкость нот зависит от их длительности.

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

Таким образом, я добавил в скетч переменные decayCounter и decay, на основе которых реализовал спад ноты таким образом, чтобы громкость нот не зависела от их длины.

Исправленную версию скетча можно скачать по ссылке.

Прослушать демо исправленной версии скетча можно, кликнув по ссылке.

Чтобы облагородить звучание, я добавил в третью версия скетча эффект изменения частоты тона в начале проигрывания ноты. Для этого я добавил переменные счётчика тона toneCounter и приращения тона toneDelta. В третьей версии скетча при проигрывании ноты частота тона увеличивается до частоты ноты. Благодаря этому эффекту звучание стало похоже на электронный бас.

Эту версию скетча можно скачать по ссылке. Прослушать демо – кликнув по ссылке.

И чтобы стало совсем похоже на настоящий синтезатор, в четвёртую версию я добавил эффект реверберации.

Размер буфера определяется константой BUFSIZE, размер которой я установил равным 1096 байт, что совпадает с приращением счётчика генератора волны для ноты до. Это даёт частоту цикла воспроизведения буфера равной 14.

26 Гц для частоты дискретизации 15625 Гц. Такой размер буфера даёт более музыкальный саунд, нежели например буфер размером 1024 байта.

В целом звучание получилось весьма винтажным, прослушать демо можно по ссылке.

Версия скетча с ревербератором.

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

Для регулировки фильтра я добавил в схему ещё один потенциометр, подключив его к выводу Analog In 1.

Версия скетча с цифровым фильтром.

Демо скетча с фильтром.

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

Строго говоря, это не совсем линейный выход, а скорее выход для наушников с очень тихой громкостью, поскольку линейный выход – это выход с напряжениями плюс/минус 1 Вольт, а в данном случае напряжение только позитивной полярности. Но для этого потребовалось бы либо собрать мостовую схему с виртуальной землёй, либо использовать операционный усилитель с двуполярным питанием, каковое получать от преобразователя напряжения.

Тем не менее, поскольку линейный вход АЦП звуковой карты обычно оснащён автоматической подстройкой нуля (это сделано чтобы не возникало так называемых смещений несущей), то такой вариант как вариант дешёво и сердито я для этой проекта счёл вполне приемлемым.

Версия скетча со стереофоническим ревербератором.

Демо скетча со стереофоническим ревербератором.

Источник: https://sites.google.com/site/vanyambauseslinux/arduino/arduino-generator-signalov

Генератор прямоугольных импульсов ver.2 — Community «Arduino для автомобиля» on DRIVE2

Прошлый генератор прямоугольных импульсов на энкодере (“GENERATOR ver.1”) получился неплохим, но ограничение в 8 кГц кого то может не устроить. Конечно, можно его разогнать до 32 кГц, но для этого придется подключать его к компьютеру и лезть в скетч, менять настройки вручную. Для самодостаточного устройства это большой минус.

Мне товарищем AlexBeda было сделано замечание, что разработанный им на Ардуино генератор “выжимал и гораздо больше частоты”. Расценив это замечание как команду к действию, решил попробовать.
Выдалось свободное время, и я смог приспособить его программу для использования в автономном устройстве.

Пришлось доработать скетч для вывода информации на LCD экран через I2C шину, урезать лишнее (на мой взгляд) и поправить некоторые ошибки. Учитывая, что я далеко не программист, пришлось попотеть, подгоняя содержимое топором и граблями.

Кто не знает, как подключить I2C модуль к Ардуино, советую посмотреть видео «Подключаем текстовый LCD 16×2 к arduino по I2C».

Встречайте, генератор прямоугольных импульсов от 1 Гц до 2 МГц с возможность регулировки скважности — “GENERATOR ver.2”. Так же как и первый генератор, все собрано из готовых блоков и трех кнопок, никакой пайки и дополнительных радиоэлементов, что облегчает сборку даже новичку.

Ну и соблюдена основная концепция изделия, оно должно быть изготовлено из ОЧЕНЬ дешевых составляющих. Поэтому недешевый модуль генератора на микросхеме ad9850 здесь не предполагалось использовать.

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

С ростом вырабатываемой данным генератором частоты, незначительно растет погрешность, падает скважность и фронты начинают заваливаться, но в основном генератор вполне точен, выполняет свои функции и позволяет вносить коррективы кнопками. Если бы еще заставить его выдать синусоиду, пилу и лопату :).

Пока думаю, как в старом корпусе расположить кнопки, однако все опробовано и протестировано подключением к осциллографу.

Zoom

Частота 100кГц

Zoom

Частота 200кГц

Zoom

Частота 1 МГц

Zoom

Частота 2 МГц, скважность подправлена до 50%

Состав: Ардуино Nano, LCD 1602 дисплей, I2C модуль, кнопки без фиксации, соединительные провода для макетной платы, корпус и модифицированная программа AlexBeda.

Мне не удалось заставить его показывать скважность в процентах (только в относительных цифрах от 1 до 255, по умолчанию поставил 128=50%), если есть решение, прошу подсказать. Может, кто увидит косяки или лишнее в программном коде, прошу высказаться.

И самое главное, я не придумал где его применить (кроме стенда для промывки форсунок, калибровки щупов осциллографа и проверки шаговых двигателей), подскажите :).А теперь, как заведено, скетч в сообщении.

____________________________________________________

Источник: https://www.drive2.com/c/487780742550520401/

DueSimpleWaveformGenerator

Simple Waveform Generator with Arduino Due

This tutorial shows how to make a simple waveform generator using the Arduino and the DAC features of the Arduino Due board.

With push buttons, you will be able to choose a waveform shape (sine, triangular, sawtooth, or square) on both DAC channels and change the frequency of the generated signal.

Hardware Required

  • Arduino Due
  • 10 kilohm potentiometer
  • 2 push buttons
  • 2 x 10 kilohm resistors
  • jumper wires

Circuit

Connect power and ground on your breadboard to the Arduino. In the image below, the red (power) and black (ground) wires connect to the two long vertical rows on the breadboard, providing access to 3.3V and ground.

Connect a wire from digital pin 2 to one leg of a pushbutton. That same leg of the button connects through a pull-down resistor (10-kilohm) to ground. The other leg of the button connects to the 3.3V power.

Wire up another button in the same fashion, but to digital pin 3.

Hook up the potentiometer by connecting one side to power and the other side to ground. The pin in the middle of the potentiometer goes to analog input 0.

Pins DAC0 and DAC1 wil generate the waveform.

Code

The waveforms are stored inside a two-dimensional array where each row represent a different waveform shape. The waveform samples are contained inside the columns, so you can access the waveform table using two indexes:

waveformsTable[waveformIndex][samplesIndex]

With the waveformIndex array, you choose which samples to read. By incrementing the sampleIndex array from 0 to the maximum in a fixed time, you will create the waveform shape. Repeating this procedure continuously and sending the samples values on the DAC output will give you a constant signal.

In order to choose the waveform shape with a push button, match the button press to the waveformIndex increment. You can use the interrupts, triggering the the press event using the RISING option for easy access. So, when the Arduino Due sees a rising edge on the button pin, it will execute the function linked to the interrupt matched with the button:

void waveCh0_select() and void waveCh1_select()

The potentiometer connected to analog pin 0 is used to choose the sample rate and the period of the singal is given by the sample rate multiplied for the number of the samples.

Taking into account the time for the instructions to execute, and adding the time for the analog input (around 40 µS to read the pot), maximum frequency for the signal with this sketch is around 170 Hz.

The sketch is composed of two files. One has the two-dimensional arrays, with the table of the samples for all the waveforms for legibility.

Download the attached file, or if you want to start from scratch you have to create a new folder inside your sketchbook folder and place the two files inside.

The sketch file must have the same name of the folder, and the file with the sample table must be named “Waveforms.h”.

FunctionGenerator.ino

/*   Simple Waveform generator with Arduino Due   * connect two push buttons to the digital pins 2 and 3     with a 10 kilohm pulldown resistor to choose the waveform     to send to the DAC0 and DAC1 channels   * connect a 10 kilohm potentiometer to A0 to control the     signal frequency

 */

#include “Waveforms.h”

#define oneHzSample 1000000/maxSamplesNum  // sample for the 1Hz signal expressed in microseconds

const int button0 = 2, button1 = 3;

volatile int wave0 = 0, wave1 = 0;

int i = 0;

int sample;

void setup() {

  analogWriteResolution(12);  // set the analog output resolution to 12 bit (4096 levels)
  analogReadResolution(12);   // set the analog input resolution to 12 bit

  attachInterrupt(button0, wave0Select, RISING);  // Interrupt attached to the button connected to pin 2

  attachInterrupt(button1, wave1Select, RISING);  // Interrupt attached to the button connected to pin 3
}

void loop() {

  // Read the the potentiometer and map the value  between the maximum and the minimum sample available
  // 1 Hz is the minimum freq for the complete wave
  // 170 Hz is the maximum freq for the complete wave. Measured considering the loop and the analogRead() time
  sample = map(analogRead(A0), 0, 4095, 0, oneHzSample);
  sample = constrain(t_sample, 0, oneHzSample);

  analogWrite(DAC0, waveformsTable[wave0][i]);  // write the selected waveform on DAC0

  analogWrite(DAC1, waveformsTable[wave1][i]);  // write the selected waveform on DAC1

  i++;

  if(i == maxSamplesNum)  // Reset the counter to repeat the wave
    i = 0;

  delayMicroseconds(sample);  // Hold the sample value for the sample time

}

// function hooked to the interrupt on digital pin 2

void wave0Select() {
  wave0++;
  if(wave0 == 4)
    wave0 = 0;
}

// function hooked to the interrupt on digital pin 3

void wave1Select() {
  wave1++;
  if(wave1 == 4)
    wave1 = 0;
}

Waveforms.h

#ifndef _Waveforms_h_
#define _Waveforms_h_

#define maxWaveform 4

#define maxSamplesNum 120

static int waveformsTable[maxWaveform][maxSamplesNum] = {

  // Sin wave
  {
    0x7ff, 0x86a, 0x8d5, 0x93f, 0x9a9, 0xa11, 0xa78, 0xadd, 0xb40, 0xba1,
    0xbff, 0xc5a, 0xcb2, 0xd08, 0xd59, 0xda7, 0xdf1, 0xe36, 0xe77, 0xeb4,
    0xeec, 0xf1f, 0xf4d, 0xf77, 0xf9a, 0xfb9, 0xfd2, 0xfe5, 0xff3, 0xffc,
    0xfff, 0xffc, 0xff3, 0xfe5, 0xfd2, 0xfb9, 0xf9a, 0xf77, 0xf4d, 0xf1f,
    0xeec, 0xeb4, 0xe77, 0xe36, 0xdf1, 0xda7, 0xd59, 0xd08, 0xcb2, 0xc5a,
    0xbff, 0xba1, 0xb40, 0xadd, 0xa78, 0xa11, 0x9a9, 0x93f, 0x8d5, 0x86a,
    0x7ff, 0x794, 0x729, 0x6bf, 0x655, 0x5ed, 0x586, 0x521, 0x4be, 0x45d,
    0x3ff, 0x3a4, 0x34c, 0x2f6, 0x2a5, 0x257, 0x20d, 0x1c8, 0x187, 0x14a,
    0x112, 0xdf, 0xb1, 0x87, 0x64, 0x45, 0x2c, 0x19, 0xb, 0x2,
    0x0, 0x2, 0xb, 0x19, 0x2c, 0x45, 0x64, 0x87, 0xb1, 0xdf,
    0x112, 0x14a, 0x187, 0x1c8, 0x20d, 0x257, 0x2a5, 0x2f6, 0x34c, 0x3a4,
    0x3ff, 0x45d, 0x4be, 0x521, 0x586, 0x5ed, 0x655, 0x6bf, 0x729, 0x794
  }
  ,

  // Triangular wave

  {
    0x44, 0x88, 0xcc, 0x110, 0x154, 0x198, 0x1dc, 0x220, 0x264, 0x2a8,
    0x2ec, 0x330, 0x374, 0x3b8, 0x3fc, 0x440, 0x484, 0x4c8, 0x50c, 0x550,
    0x594, 0x5d8, 0x61c, 0x660, 0x6a4, 0x6e8, 0x72c, 0x770, 0x7b4, 0x7f8,
    0x83c, 0x880, 0x8c4, 0x908, 0x94c, 0x990, 0x9d4, 0xa18, 0xa5c, 0xaa0,
    0xae4, 0xb28, 0xb6c, 0xbb0, 0xbf4, 0xc38, 0xc7c, 0xcc0, 0xd04, 0xd48,
    0xd8c, 0xdd0, 0xe14, 0xe58, 0xe9c, 0xee0, 0xf24, 0xf68, 0xfac, 0xff0,
    0xfac, 0xf68, 0xf24, 0xee0, 0xe9c, 0xe58, 0xe14, 0xdd0, 0xd8c, 0xd48,
    0xd04, 0xcc0, 0xc7c, 0xc38, 0xbf4, 0xbb0, 0xb6c, 0xb28, 0xae4, 0xaa0,
    0xa5c, 0xa18, 0x9d4, 0x990, 0x94c, 0x908, 0x8c4, 0x880, 0x83c, 0x7f8,
    0x7b4, 0x770, 0x72c, 0x6e8, 0x6a4, 0x660, 0x61c, 0x5d8, 0x594, 0x550,
    0x50c, 0x4c8, 0x484, 0x440, 0x3fc, 0x3b8, 0x374, 0x330, 0x2ec, 0x2a8,
    0x264, 0x220, 0x1dc, 0x198, 0x154, 0x110, 0xcc, 0x88, 0x44, 0x0
  }
  ,

  // Sawtooth wave

  {
    0x22, 0x44, 0x66, 0x88, 0xaa, 0xcc, 0xee, 0x110, 0x132, 0x154,
    0x176, 0x198, 0x1ba, 0x1dc, 0x1fe, 0x220, 0x242, 0x264, 0x286, 0x2a8,
    0x2ca, 0x2ec, 0x30e, 0x330, 0x352, 0x374, 0x396, 0x3b8, 0x3da, 0x3fc,
    0x41e, 0x440, 0x462, 0x484, 0x4a6, 0x4c8, 0x4ea, 0x50c, 0x52e, 0x550,
    0x572, 0x594, 0x5b6, 0x5d8, 0x5fa, 0x61c, 0x63e, 0x660, 0x682, 0x6a4,
    0x6c6, 0x6e8, 0x70a, 0x72c, 0x74e, 0x770, 0x792, 0x7b4, 0x7d6, 0x7f8,
    0x81a, 0x83c, 0x85e, 0x880, 0x8a2, 0x8c4, 0x8e6, 0x908, 0x92a, 0x94c,
    0x96e, 0x990, 0x9b2, 0x9d4, 0x9f6, 0xa18, 0xa3a, 0xa5c, 0xa7e, 0xaa0,
    0xac2, 0xae4, 0xb06, 0xb28, 0xb4a, 0xb6c, 0xb8e, 0xbb0, 0xbd2, 0xbf4,
    0xc16, 0xc38, 0xc5a, 0xc7c, 0xc9e, 0xcc0, 0xce2, 0xd04, 0xd26, 0xd48,
    0xd6a, 0xd8c, 0xdae, 0xdd0, 0xdf2, 0xe14, 0xe36, 0xe58, 0xe7a, 0xe9c,
    0xebe, 0xee0, 0xf02, 0xf24, 0xf46, 0xf68, 0xf8a, 0xfac, 0xfce, 0xff0
  }
  ,

  // Square wave

  {
    0xfff, 0xfff, 0xfff, 0xfff, 0xfff, 0xfff, 0xfff, 0xfff, 0xfff, 0xfff,
    0xfff, 0xfff, 0xfff, 0xfff, 0xfff, 0xfff, 0xfff, 0xfff, 0xfff, 0xfff,
    0xfff, 0xfff, 0xfff, 0xfff, 0xfff, 0xfff, 0xfff, 0xfff, 0xfff, 0xfff,
    0xfff, 0xfff, 0xfff, 0xfff, 0xfff, 0xfff, 0xfff, 0xfff, 0xfff, 0xfff,
    0xfff, 0xfff, 0xfff, 0xfff, 0xfff, 0xfff, 0xfff, 0xfff, 0xfff, 0xfff,
    0xfff, 0xfff, 0xfff, 0xfff, 0xfff, 0xfff, 0xfff, 0xfff, 0xfff, 0xfff,
    0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
    0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
    0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
    0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
    0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
    0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0
  }

};

#endif

 

Источник: https://www.arduino.cc/en/Tutorial/DueSimpleWaveformGenerator

Ссылка на основную публикацию
Adblock
detector
",css:{backgroundColor:"#000",opacity:.6}},container:{block:void 0,tpl:"
"},wrap:void 0,body:void 0,errors:{tpl:"
",autoclose_delay:2e3,ajax_unsuccessful_load:"Error"},openEffect:{type:"fade",speed:400},closeEffect:{type:"fade",speed:400},beforeOpen:n.noop,afterOpen:n.noop,beforeClose:n.noop,afterClose:n.noop,afterLoading:n.noop,afterLoadingOnShow:n.noop,errorLoading:n.noop},o=0,p=n([]),h={isEventOut:function(a,b){var c=!0;return n(a).each(function(){n(b.target).get(0)==n(this).get(0)&&(c=!1),0==n(b.target).closest("HTML",n(this).get(0)).length&&(c=!1)}),c}},q={getParentEl:function(a){var b=n(a);return b.data("arcticmodal")?b:(b=n(a).closest(".arcticmodal-container").data("arcticmodalParentEl"),!!b&&b)},transition:function(a,b,c,d){switch(d=null==d?n.noop:d,c.type){case"fade":"show"==b?a.fadeIn(c.speed,d):a.fadeOut(c.speed,d);break;case"none":"show"==b?a.show():a.hide(),d();}},prepare_body:function(a,b){n(".arcticmodal-close",a.body).unbind("click.arcticmodal").bind("click.arcticmodal",function(){return b.arcticmodal("close"),!1})},init_el:function(d,a){var b=d.data("arcticmodal");if(!b){if(b=a,o++,b.modalID=o,b.overlay.block=n(b.overlay.tpl),b.overlay.block.css(b.overlay.css),b.container.block=n(b.container.tpl),b.body=n(".arcticmodal-container_i2",b.container.block),a.clone?b.body.html(d.clone(!0)):(d.before("
"),b.body.html(d)),q.prepare_body(b,d),b.closeOnOverlayClick&&b.overlay.block.add(b.container.block).click(function(a){h.isEventOut(n(">*",b.body),a)&&d.arcticmodal("close")}),b.container.block.data("arcticmodalParentEl",d),d.data("arcticmodal",b),p=n.merge(p,d),n.proxy(e.show,d)(),"html"==b.type)return d;if(null!=b.ajax.beforeSend){var c=b.ajax.beforeSend;delete b.ajax.beforeSend}if(null!=b.ajax.success){var f=b.ajax.success;delete b.ajax.success}if(null!=b.ajax.error){var g=b.ajax.error;delete b.ajax.error}var j=n.extend(!0,{url:b.url,beforeSend:function(){null==c?b.body.html("
"):c(b,d)},success:function(c){d.trigger("afterLoading"),b.afterLoading(b,d,c),null==f?b.body.html(c):f(b,d,c),q.prepare_body(b,d),d.trigger("afterLoadingOnShow"),b.afterLoadingOnShow(b,d,c)},error:function(){d.trigger("errorLoading"),b.errorLoading(b,d),null==g?(b.body.html(b.errors.tpl),n(".arcticmodal-error",b.body).html(b.errors.ajax_unsuccessful_load),n(".arcticmodal-close",b.body).click(function(){return d.arcticmodal("close"),!1}),b.errors.autoclose_delay&&setTimeout(function(){d.arcticmodal("close")},b.errors.autoclose_delay)):g(b,d)}},b.ajax);b.ajax_request=n.ajax(j),d.data("arcticmodal",b)}},init:function(b){if(b=n.extend(!0,{},a,b),!n.isFunction(this))return this.each(function(){q.init_el(n(this),n.extend(!0,{},b))});if(null==b)return void n.error("jquery.arcticmodal: Uncorrect parameters");if(""==b.type)return void n.error("jquery.arcticmodal: Don't set parameter \"type\"");switch(b.type){case"html":if(""==b.content)return void n.error("jquery.arcticmodal: Don't set parameter \"content\"");var e=b.content;return b.content="",q.init_el(n(e),b);case"ajax":return""==b.url?void n.error("jquery.arcticmodal: Don't set parameter \"url\""):q.init_el(n("
"),b);}}},e={show:function(){var a=q.getParentEl(this);if(!1===a)return void n.error("jquery.arcticmodal: Uncorrect call");var b=a.data("arcticmodal");if(b.overlay.block.hide(),b.container.block.hide(),n("BODY").append(b.overlay.block),n("BODY").append(b.container.block),b.beforeOpen(b,a),a.trigger("beforeOpen"),"hidden"!=b.wrap.css("overflow")){b.wrap.data("arcticmodalOverflow",b.wrap.css("overflow"));var c=b.wrap.outerWidth(!0);b.wrap.css("overflow","hidden");var d=b.wrap.outerWidth(!0);d!=c&&b.wrap.css("marginRight",d-c+"px")}return p.not(a).each(function(){var a=n(this).data("arcticmodal");a.overlay.block.hide()}),q.transition(b.overlay.block,"show",1*")),b.overlay.block.remove(),b.container.block.remove(),a.data("arcticmodal",null),n(".arcticmodal-container").length||(b.wrap.data("arcticmodalOverflow")&&b.wrap.css("overflow",b.wrap.data("arcticmodalOverflow")),b.wrap.css("marginRight",0))}),"ajax"==b.type&&b.ajax_request.abort(),p=p.not(a))})},setDefault:function(b){n.extend(!0,a,b)}};n(function(){a.wrap=n(document.all&&!document.querySelector?"html":"body")}),n(document).bind("keyup.arcticmodal",function(d){var a=p.last();if(a.length){var b=a.data("arcticmodal");b.closeOnEsc&&27===d.keyCode&&a.arcticmodal("close")}}),n.arcticmodal=n.fn.arcticmodal=function(a){return e[a]?e[a].apply(this,Array.prototype.slice.call(arguments,1)):"object"!=typeof a&&a?void n.error("jquery.arcticmodal: Method "+a+" does not exist"):q.init.apply(this,arguments)}}(jQuery)}var debugMode="undefined"!=typeof debugFlatPM&&debugFlatPM,duplicateMode="undefined"!=typeof duplicateFlatPM&&duplicateFlatPM,countMode="undefined"!=typeof countFlatPM&&countFlatPM;document["wri"+"te"]=function(a){let b=document.createElement("div");jQuery(document.currentScript).after(b),flatPM_setHTML(b,a),jQuery(b).contents().unwrap()};function flatPM_sticky(c,d,e=0){function f(){if(null==a){let b=getComputedStyle(g,""),c="";for(let a=0;a=b.top-h?b.top-h{const d=c.split("=");return d[0]===a?decodeURIComponent(d[1]):b},""),c=""==b?void 0:b;return c}function flatPM_testCookie(){let a="test_56445";try{return localStorage.setItem(a,a),localStorage.removeItem(a),!0}catch(a){return!1}}function flatPM_grep(a,b,c){return jQuery.grep(a,(a,d)=>c?d==b:0==(d+1)%b)}function flatPM_random(a,b){return Math.floor(Math.random()*(b-a+1))+a}