Официальный сайт компании Arduino по адресу arduino.cc
Генерация музыкального звука на Atmega328
- Войдите или зарегистрируйтесь, чтобы получить возможность отправлять комментарии
Требуется генерить периодический сигнал с регулируемой скважностью и частотой, изменяемой в пределах 16-4160 Гц с точностью не хуже 0.2%. В наихудшем случае указанная погрешность должна обеспечиваться до частот 1 кГц, увеличиваясь к 4 кГц не более чем до 0.4%.
По диапазону и точности идеально подходит режим таймера 1 Phase Correct PWM с коэффициентом предделения 8.
Столкнулся с проблемой при плавной перестройке частоты - возникают нерегулярные помехи, проявляющиеся как легкие щелчки или "провалы" сигнала. Т.е. нарушается регулярность (периодичность) сигнала. В принципе, это заметно как на слух, так и на осциллографе.
Управление частотой звука (вибрато) осуществляется по прерыванию. Но прерыванием я пользуюсь стандартным - от 0 таймера по совету ЕвгенийП http://arduino.ru/forum/programmirovanie/etyudy-dlya-nachinayushchikh-me...
Т.е. нужно править включаемые файлы.
Как вариант - запрограммировать на прерывание таймер 2.
К аналоговому входу А1 нужно подключить потенциометр 0-5В (регулирует глубину вибрато), а к 9 пину через делитель примерно 1/10 вход аудио (осциллограф).
unsigned long icr; // ICR+1; unsigned char vibrato = 0; void Every_1024us(const unsigned long ii) { static int counter; // при частоте вызовов 978 Гц частоте 7 Гц соотв. период 140 тиков if (counter < 70) { ICR1=(icr-1) + (counter - 35)*vibrato/64; } else { ICR1=(icr-1) + (105 - counter)*vibrato/64; } OCR1A = ICR1/7; // скважность 1/7 counter++; if(counter == 140) counter = 0; } void setup() { Serial.begin(115200); pinMode (9,OUTPUT); // выход генератора PB2 TCCR1A=1<<COM1A1; //подключить выход OC1A первого таймера TCCR1B=2|(1<<WGM13);; // // divider=8; icr = 2273; Timer0_Hook = Every_1024us; } void loop() { delay(500); vibrato = analogRead(A0)/4; Serial.print(vibrato); Serial.print('\t'); Serial.println(35*vibrato/64); }
Предполагаемые источники проблемы:
1. При занесении коэффициента деления в 16-разрядный регистр таймера старший и младший байты заносятся неодновременно, в результате чего коэффициент оказывается совсем не тем, который нужен.
2. В момент, когда таймер почти досчитал до конца, в регист заносится меньшее число, в результате чего тайме "пропускает" момент окончания счета.
Возможны ли какие другие источники проблемы?
Как с этим бороться?
andriano, думаю ситуация (1) маловероятна, иначе бы везде писали про возможность такой проблемы. А вот то что у вас ICR1 и OCR1A записываются не одновременно ( и не синхронизируясь по счёту таймера1) -может повлиять, т.к. эти регистры перечитываются при переходе TCNT1 через ноль. Если новое значение ICR1 прочиталось, а OCR1A не успело -как раз может быть пропуск. Можно попробовать произвести рассчёт значений ICR1 OCR1A записав новые значения во временные переменные. Затем последовательными командами остановить таймер1, переписать регистры ICR1 OCR1A и запустить таймер1.
andriano, по идее остановка таймера и передача трёх команд затянет текущий фронт импульса где-то на 5 тактов МК. Вряд ли это можно будет уловить слухом ) В любом случае попробуйте остановить таймер перед записью регистров, хотя бы в качестве эксперимента.
А вот то что у вас ICR1 и OCR1A записываются не одновременно ... Если новое значение ICR1 прочиталось, а OCR1A не успело -как раз может быть пропуск.
Решил проверить.
Я так понимаю, проверка должна состоять в том, что если убрать эту неодновременность, то должен исчезнуть и дефект.
Один из способов убрать неодновременность - вообще отказаться от изменени OCR1A - пусть скважность будет плавать вместе с частотой. Собственно, для меня такой способ проверки проще, т.к. я еще не разобрался, как остановить таймер, а потом пустить его с того же места.
Перенес установку OCR1A из прерывания в setup. Дефект остался. Так что думаю, одновременность тут ни при чем.
Да, провал в генерации - где-то порядка 30 мс, т.е. очень похоже на полный полупериод счетчтика, считающего до упора (до 65535).
Да, еще: по 5 изданию Евстифеева Микроконтроллеры AVR семейств Tiny и Mega фирмы ATMEL
стр. 298 (13.6.3.5. Режим Phase and Frequency Correct PWM)
Вот так4ая вот рекомендация. А я меняю модуль счета почти 1000 раз в секунду.
Только этот совет я не понял, как реализовывать. Если мы использовали ICR1 для задания периода, а OCR1A - полупериода, то если использовать OCR1A для периода, что использовать для полупериода? OCR1B? ICR1? Во втором случае полдучается хрен редьки не слаще, т.к. оба регистра нужно менять с одинаковой частотой.
andriano, по идее остановка таймера и передача трёх команд затянет текущий фронт импульса где-то на 5 тактов МК. Вряд ли это можно будет уловить слухом ) В любом случае попробуйте остановить таймер перед записью регистров, хотя бы в качестве эксперимента.
Ладно, завтра снова буду перечитывать Евсифеева, и как только пойму, как остановить счет, не обнуляя регистр, попробую.
Сегодня же думаю, раз регистр обновляется при переходе через 0, значит, в этот момент и может происходить сбой. Думаю запрещать переустановку частоты, если до конца счета осталось 1-3 тика таймера. Кстати, как отличить число 3, считанное из счетчика в полупериод его инкркмента, от такого же 3, считанного во время декремента?
Ладно, завтра снова буду перечитывать Евсифеева, и как только пойму, как остановить счет, не обнуляя регистр, попробую.
Не обнуляя чего?? Остановить просто - TCCR1B=0, снова запустить TCCR1B=18; Между ними обновить ICR1, делов-то на пять минут проверить этот вариант.
Пойду посмотрю.
Но у меня сомнения по поводу того, что это будет незаметно на слух. Сейчас дефекты большие, но их частота менее 1 в секунду. Если мы будем останавливать таймер, то это 1000 раз в секунду. Т.е. с такой частотой у нас будет увеличиваться период. Возникнут интермодуляционные искажения - появятся негармонические частоты равные суммам и разностям частот музыкального тона и частоты прерываний. А негармонические частоты очень заметны.
Это жуть - теперь сигнал состоит практически из одних помех.
andriano, ну значит остался последний вариант - перепрограммировать ICR1 сразу после перехода TCNT1 через 0. Думаю корректно это можно сделать только в собственном прерывании по переполнению тамера1. TIMSK1=1<<TOIE1; ISR (TIMER1_OVF_vect) { ..... }
Сегодня читал не Евстфеева, а оригинальную 660-страничную документацию.
То ли оригинал всегда лучше копии, то ли "повторение мать учения", но выход нашел. Собственно, мы зациклились на переходе через 0, и в этом была наша ошибка, т.к. отслеживать имеет смысл, наоборот, переход через TOP.
Вот такой фрагмент решил проблему.
Альтернативная ветка (т.е. отказ от обновления регистров) реализуется примерно в 1.5% случаев, при этом обновление происходит не через 1 мс, а через две.
Но при повышении ноты (увеличении частоты) этот процент будет увеличиваться.
Поэтому настораживает, что выполняется этот фрагмент целых 32 такта. Ладно бы такта процессора, так ведь такта предделителя на 8, что равняется 256 тактам процессора. Что-то одного условного и четырех операторов присвоения, мне кажется, многовато. Буду копать ассемблерный код того, что скомпилировалось.
andriano, что то я не соображу, чем переход через ноль отличается от перехода через TOP? Их же по идее разделяет всего один такт МК.
Нет, там сначала счетчик инкрементируется, а потом, когда дойдет до TOP, - декрементируется. Собственно, корректность фазы вытекает как раз из симметричности процесса. Ну и дополнительная двойка в формуле для частоты - оттуда же: один раз считается до ICR1 вверх, а потом от него - вниз до 0. В все это один период выходной частоты.
Выяснил, откуда ~256 тактов в фрагменте из 4 строк.
Оказывается оптимизатор строку 1 переместил после строки 5. В принципе - логично: раз результат вычисления используется только внутри условия, то и незачем его считать снаружи - вдруг не понадобится. Т.е. он соптимизировал общее время выполнения, тогда как мне нужно соптимизировать время между чтением TCNT1 и записью ICR1. Точное количество тактов посчитать не удалось - там вызов процедуры деления, а это дело долгое.
переписал строку 1 так:
такое деление выполняется через сдвиги, а потому намного быстрее.
Вместо 32 тактов получил 13.
Но все равно непонятно: это порядка 100 тактов процессора, а по листингу около 62 операций (считая одну 4-байтовую за две). Хорошо хоть, запись в ICR1 (строка 4) сдедует сразу после условного перехода (строка3), в общем, со всем вычислением примерно 20 команд.
Уменьшил зазор в строке 3 с 30 до 3 тактов. За то время, пока писал, произошло 3 дефекта. Т.е. один раз в несколько минут. Надо к этой тройке добавить еще шаг изменения регистра ICR1, и, думаю, все будет нормально. А шаг учитывать нужно, т.к. при более высоких нотах он будет больше.
dimax, спасибо за помощь.
(я еще обращусь)
Теперь хочу заставить генерить звук таймер 2. Проблема в том, что 256 - недостаточное разрешение для ноты даже в пределах одной октавы, а там, учитывая сетку значений предделителя, нужно обеспечить перестройку при одном коэффициенте до 3 октав.
Пока идея формировать коэффициент деления как произведение коэффициента таймера, который генерит прерывания, и софтового счетчика, который изменяется по этим прерываниям и в некоторые моменты программно же устанавливает пин в 0 или 1.
Кстати, какие нибуд протоколы типа SoftwareSerial или I2C не используют таймер 2?
такое деление выполняется через сдвиги, а потому намного быстрее.
Что через сдвиги быстрее это понятно, только не вполне очевидно, как мне кажется, что такой код выполняется через сдвиги.
Т.е. для исключения неоднозначности предлагаю таки делать такую запись в виде сдвигов
Ну, вообще-то, если при формальной замене одной операции деления на две время выполнения сокращается в 2.5 раза, мне кажется, диагноз очевиден.
Но для сомневающихся могу привести фрагмент листинга