Автор Тема: Формирование сигналов (принцип DDS и PWM)  (Прочитано 40648 раз)

mim

  • Hero Member
  • *****
  • Сообщений: 2700
    • Просмотр профиля
Re:Формирование сигналов (принцип DDS и PWM)
« Ответ #10 : Май 20, 2011, 09:25:31 »
Сегодня мы подошли вплотную к поставленной задаче.
Аргумент функции sin или фаза, в отличие от значения функции, меняется во времени линейно. Сформировать линейно меняющуюся во времени последовательность кодов гораздо проще (что мы и сделали в примере выше).
Однако такая зависимость вряд ли кого-то устроит. Кому нужен синус с таким шагом частоты (1027,96 Гц)? Я думаю – никому.
Неплохо было бы (для начала) создать генератор, который позволяет менять частоту с шагом, хотя бы, в 1 Гц (или близко к этому).
Для реализации такой возможности необходимо, как бы, растянуть шкалу фазы - масштабировать.
Делается это с помощью «накапливающего сумматора». В инете на эту тему есть много информации, но понять ее с первого раза удается не всегда. Поэтому я и построил все выше сказанное, так, чтобы было на примерах и доходчиво.

Накапливающий сумматор у нас почти реализован, но требует небольшой доработки.
Вот он (в примерах выше):

portb=SAMPLES[(i&0x3F)];
i+=FCV_DISKRET;

В развернутом виде этот кусочек должен был выглядеть следующим образом:

portb=SAMPLES[А];
i = i+FCV_DISKRET;
А = i&0x3F;


Но тогда это будет работать дольше.

Что же такое «накапливающий сумматор» или, как его еще называют, «аккумулятор фазы» (выделен жирным).
Представьте себе переменную, например, 16 разрядов (рис.1). Если ее значение увеличивать каждый раз на переменную FCV_DISKRET и читать значение по маске, то считываемое значение будет лежать в пределах от 0 до 63 и меняться с частотой прерывания.
А если маску перенести на 10 разрядов выше (рис. 2), то частота чтения переменной «i» по маске уменьшится. Потому что суммирование будет длиннее. Это напоминает делитель, как у таймеров. Однако значение переменной будет намного больше допустимой размерности массива (потому что будет находится в старших разрядах).
Поэтому каждый раз при чтении переменной по маске, необходимо сдвигать переменную «i» на 10 разрядов левее (делить на 1024). Таким образом приводить значение в диапазон от 0 до 63 (рис. 3).
 




Накапливающий сумматор это переменная «А», которая в каждом такте работы устройства перезагружается величиной, равной старому содержимому, плюс некоторая постоянная добавка. Выходной код аккумулятора фазы представляет собой код мгновенной фазы выходного сигнала (индекс массива). Постоянная добавка, которая используется при работе аккумулятора фазы, представляет собой приращение фазы за один такт работы устройства. Чем быстрее изменяется фаза во времени, тем больше частота генерируемого сигнала. Поэтому значение приращения фазы фактически является кодом выходной частоты.
Написано заумно, а выглядит практически так.

portb=SAMPLES[(i>>10)&0x3F];
i+=FCV_DISKRET;

Очевидно, что если значение аккумулятора разделить на число, кратное разрядам сдвига – 2,4,8,16….. 1024… (такое деление самое быстрое), то можно получить различные значения шага частоты.
Очень нас устраивает число 1024. При таком делении 1027,96 / 1024 мы получим шаг частоты 1,003868 Гц.
Кроме того, поскольку при сдвиге на 10 разрядов влево в старших разрядах останется только число в диапазоне от 0 до 63, то маску можно убрать.

portb=SAMPLES[(i>>10)];
i+=FCV_DISKRET;


Ниже приведен пример генератора синусоиды с шагом в 1,003868 Гц.

 

Частота генерации равна F = DISKRET*1,003868 Гц;

Продолжение следует (после выходных).
« Последнее редактирование: Июль 19, 2014, 19:27:57 от admin »

mim

  • Hero Member
  • *****
  • Сообщений: 2700
    • Просмотр профиля
Re:Формирование сигналов (принцип DDS и PWM)
« Ответ #11 : Май 23, 2011, 10:03:16 »
Ну, раз вопросов нет, значит или все понятно, или на хрен никому не нужно…

Еще один пример. В этом примере аккумулятор имеет 11 разрядов.
Шаг 0,502179  Гц
 

Переменная i определена как Long. Таким образом, счетчик аккумулятора выглядит следующим образом.

portb=SAMPLES[(i>>11)&0x3F];
i+=FCV_DISKRET;

Прерывание …….Базовая частота……Шаг………...…..Аккумулятор
65789,47………….1027,960469 ………….1,003868 …….…….1024 ….10
65789,47………….1027,960469 ………….0,502179 ……….….2047 ….11
65789,47………….1027,960469 ………….0,251028 ……….….4095 ….12
65789,47………….1027,960469 ………….0,125499 …….…….8191 ….13
65789,47………….1027,960469 ………….0,062742 ………….16384 ….14
65789,47………….1027,960469 ………….0,031372 ………….32767 ….15


Однако дальнейшее увеличение сдвига (увеличит расчеты) приведет к задержке прерывания и собьет частоту прерывания – генерация синуса не будет соответствовать расчетной. Кроме того, нужно будет увеличивать и размер переменной DISKRET.
Постр номер 10075 об этом красноречиво говорит. Если Уважаемый koms45 нам поможет, то можно нарастить тему.
Обратите внимание, что определение массива синуса перенесено в ОЗУ. Поскольку мы увеличили переменную (и получили дополнительные вычисления), то нужно уменьшить время обращения к массиву. Обращение к РОМ массиву занимает много времени. Связано это с тем, что для работы с РОМ в ПИКа применяется специальная процедура (она описана в библиотеке компилятора). Кстати, у АВР это аналогично, но требует еще специального обращения к ней.
Ниже приведен пример, когда частота прерывания равна 50000 Гц.
Базовая частота 50000/64=781,25 Гц
Аккумулятор 13 разрядов (8191) – шаг 0,095379 Гц
 

Еще один пример с шагом 0,502179 Гц, но частота дискретизации (прерывания ) 32894,736 гц. Шаг уменьшен за счет дискретизации, а не за счет наращивания аккумулятора.
 

Продолжение следует.
« Последнее редактирование: Июль 19, 2014, 19:28:42 от admin »

mim

  • Hero Member
  • *****
  • Сообщений: 2700
    • Просмотр профиля
Re:Формирование сигналов (принцип DDS и PWM)
« Ответ #12 : Май 24, 2011, 08:55:49 »
Вот мы и подошли вплотную к вопросу (первый пост в теме) формирования многоголосного звука (полисинусоиды).
В примере показана возможность формирования 4 канального синусоидального сигнала.
 
Вот так выглядит сумма сигналов

portb =   (int)(SAMPLES[(i_A>>8)&0x3F]
      +SAMPLES[(i_B>>8)&0x3F]
      +SAMPLES[(i_C>>8)&0x3F]
      +SAMPLES[(i_D>>8)&0x3F])>>2;

(int) - временное изменение размера переменной, в процессе сложения четырех сигналов может возникнуть переполнение байтовой переменной.
>>2 – деление на 4, потому что 4 сигнала и сумму амплитуд делим на 4.

В данном примере аккумулятор равен 8 разрядам.

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

Построение программ базируется на описаниях приведенных в предыдущих постах, недостающее - в комментариях внутри программ.
Продолжение следует.
« Последнее редактирование: Июль 19, 2014, 19:29:51 от admin »

rajevwik

  • Newbie
  • *
  • Сообщений: 42
    • Просмотр профиля
Re:Формирование сигналов (принцип DDS и PWM)
« Ответ #13 : Май 25, 2011, 06:35:10 »
Вопрос не в тему , но близко к теме! По данной ссылке http://cxem.net/izmer/izmer75.php  описывается DDS генератор на AVR AT90S2313. Доступен исходник на ассемблере под GCC=AVR, как получить из него *.HEX ?,не получается у меня, может кто ассемблирует исходник, спасибо за помошь!

rajevwik

  • Newbie
  • *
  • Сообщений: 42
    • Просмотр профиля
Re:Формирование сигналов (принцип DDS и PWM)
« Ответ #14 : Май 25, 2011, 06:37:55 »
Пролетел с прикрепленным файлом, повторяю попытку

mim

  • Hero Member
  • *****
  • Сообщений: 2700
    • Просмотр профиля
Re:Формирование сигналов (принцип DDS и PWM)
« Ответ #15 : Май 25, 2011, 08:56:06 »
А почему бы тебе самому не написать все это, основываясь на выше изложенном (если ты конечно читал все это, тем более что на АВР такая реализация есть).
Тем более мне не понятна необходимость привязываться к компьютеру.
Почему не автономное устройство?

mim

  • Hero Member
  • *****
  • Сообщений: 2700
    • Просмотр профиля
Re:Формирование сигналов (принцип DDS и PWM)
« Ответ #16 : Май 25, 2011, 10:50:02 »
К сожалению без теории ничего не может случится само собой, такова философия жизни. Раз уж я затеял эту вознь, то продолжаю (авось кому сгодится).

Мы рассмотрели принцип формирования сигнала по принципу прямого цифрового синтеза частоты.
Продолжим работать с ПИКами и рассмотри этот же принцип, но с формированием сигнала на основе ШИМ (в качестве АЦП).
Для формирования ШИМ сигнала в ФК используется специальный компонент PWM.

Что происходит, когда вы используете модуль PWM?

В свойствах компонента вы должны определить значения некоторых регистров, которые участвуют в работе модуля CCP МК для реализации режима ШИМ.
По правой кнопке мыши вы входите в дополнительные свойства компонента.
В свойствах компонента нужно задать значение «Регистра периода» - pr2 и значение «Предделителя», значение предделителя определит биты в регистре управления t2con счетчика таймера TMR2. В режиме ШИМ таймер TMR2 служит опорным счетчиком для модуля ССР.
Название регистра pr2 говорит само за себя, меняя его значение, мы изменяем частоту ШИМ в пределах, заданных значением делителя частоты в управляющем регистре t2con.
 




Управляющий регистр t2con определяет режим работы таймера TMR2. В регистр t2con заносятся значения, которые определяют:
- включен таймер TMR2 или нет (бит TMR2ON, это бит 2);
- коэффициенты выходного делителя TMR2 (постделителя) и входного делителя TMR2 (предделителя).
Поскольку в режиме ШИМ выходной делитель не используется то в разряды t2con (TOUTPS3 : TOUTPS0, это 3 – 6 биты) будут записаны нули.

Таким образом, например, установив в свойствах компонента PWM значения периода равное 75 и предделителя равное 1 (T2CKPS1 : T2CKPS1, это 0 и 1 бит), при частоте кварца 20 МГц, мы получим частоту ШИМ равную 65789,47 Гц. Значения регистров будут следующие:

pr2 = 75;
t2con = 0x00
// определить предделитель как 1:1, TMR2 – отключен.

Видно, что в регистре t2con (все значения равны нулю) таймер TMR2 выключен, бит TMR2ON равен нулю и деление тактирующей частоты МК определено как 1 (т.е. значения битов 0 и 1 равно нулю).
Для того чтобы включить модуль ССР в режим генерации и передать в него определенные выше значения, нужно воспользоваться макросом Enable(Char). В макросе Enable необходимо указать, какой из модулей ССР (выходов) вы будете использовать. После этого в программе будут определены значения регистров ccp1con или ccp2con, а также направление работы портов выводов ССР и самое главное в регистр t2con будет записан, бит TMR2ON включения таймера TMR2, который является самым главным модулем работы модуля ССР в режиме ШИМ.
Вот так будут определены значения регистров в макросе Enable:

pr2 = 75;

ccp1con = 0x0C; // включить режим ШИМ на CCP1
   tmr2=0; // обнулить регистр таймера
или

ccp2con = 0x0C; //включить режим ШИМ на CCP2
   tmr2=0; // обнулить регистр таймера

t2con = 0x04; // включить таймер TMR2, и определить предделитель как 1:1

Дальнейшее управление ШИМ осуществляется через макрос SetDutyCycle(Char1, Char2), этот компонент определяет для какого выхода CCP Char1 нужно изменить значение длительности импульса Char2. Первая переменная Char1 определяет значение nIdx (номер CCP), вторая переменная Char2 определяет значение nDuty (длительность импульса). Внутри макроса будет использоваться только одно из условий:

if (nIdx == 1)
    {
        ccpr1l = nDuty;
    }


или

if (nIdx == 2)
    {
        сcpr2l = nDuty;
    }


Переменная nDuty передает в регистр ccpr1l или ccpr2l длительность импульса ШИМ. Регистр ccpr1l (ccpr2l) так и называется – регистр длительности импульса.
В дальнейшем мы будем применять прямое обращение к регистрам ccpr1l или ccpr2l, а не через макрос, это ускорит работу подпрограммы прерывания.

Обращение к макросу SetDutyCycle (к регистру ccpr1l (ccpr2l)) нужно организовать через прерывание. В прерывании мы будем изменять значение регистра длительность импульса и тем самым менять скважность ШИМ. Эти значения будут определять амплитуду синусоиды и размещаются в массиве SAMPLES размерностью 64 байта.

Продолжение следует..

mim

  • Hero Member
  • *****
  • Сообщений: 2700
    • Просмотр профиля
Re:Формирование сигналов (принцип DDS и PWM)
« Ответ #17 : Май 26, 2011, 09:12:07 »
Как определяется режим прерывания?

Поскольку мы уже используем таймер TMR2, то и прерывание будем организовывать по таймеру TMR2. На рис… показана структурная схема таймера, на которой отображены все блоки, которые участвуют в определении временных рамок работы прерывания.
После определения режима ШИМ, будет определен и режим работы таймера TMR2 (напомню, мы установили частоту ШИМ 65789,47 Гц, она же и будет на выходе таймера), но остается неопределенным значение выходного делителя и не включен режим прерывания.
 




Хотя в ФК есть возможность напрямую определять прерывание по TMR2, мы напишем свое. Использование стандартного модуля TMR2 в данном случае вносит некоторые неудобства. Вот что получается, если применить его напрямую.
//Interrupt
   //Interrupt: Enable TMR2
   st_bit(intcon,PEIE);      // разрешили периферийные прерывания
   cr_bit(t2con,T2CKPS0);      // установили Предделитель без деления
   cr_bit(t2con,T2CKPS1);      
   st_bit(t2con,TMR2ON);      // включили TMR2
   t2con = (t2con & 0x07) | 0x18;   // установили Постделитель – деление на 4
   pr2=256-1;         // определение регистра периода
   st_bit(intcon,GIE);      // разрешили прерывания глобально
   st_bit(pie1, TMR2IE);      // разрешили прерывание от TMR2

В исходном коде, помечено красным, видно, что получится повторное определение значений регистров (но это не так страшно, да и компилятор, я думаю, это подчистит). Но самым неудобным является то, что значение регистра периода можно определить только значениями – 16, 32, 64, 128, 256. Эти значения не совпадают с тем значением, которое определено нами выше pr2 = 75. Таким образом, нам придется опять писать строку на Си pr2 = 75; после модуля прерывания.
Кроме того, программой будет сгенерировано прерывание следующего вида:

//Handler code for [TMR_2]
   if (ts_bit(pir1, TMR2IF) && ts_bit(pie1, TMR2IE))
   {
   FCM_To_port();
   cr_bit(pir1,TMR2IF);
   }


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

Код разрешения мы перепишем так:
st_bit(intcon,PEIE);      // разрешили периферийные прерывания
   t2con = (t2con & 0x07) | 0x18;   // установили Постделитель – деление на 4
   st_bit(intcon,GIE);      // разрешили прерывания глобально
   st_bit(pie1, TMR2IE);      // разрешили прерывание от TMR2

Код запрещения:
cr_bit(piе1, TMR2IE);       // запрет прерывания

Код поддержки:
FCM_%n();
cr_bit(pir1,TMR2IF); эту строку для ПИК лучше записать так – «pir1.TMR2IF=0;»

FCM_%n();
pir1.TMR2IF=0;



В результате таких определений мы получим ШИМ с частотой 65789,47 Гц и прерывание с частотой 16447,37 гц.

Вот что получим.

 


Ниже приведен пример синуса ШИМ. Моделирование занимает более длительный процесс.

 

Расчет синуса в зависимости от предельного значения регистра периода.
 


Еще один пример с частотой ШИМ в два раза большей чем в предыдущем (значение регистра периода равно 37).
Для того чтобы не пересчитывать таблицу синуса (она расчитана для регистра периода 75) применяется деление на 2 самих значений синуса.
ccpr2l =((SAMPLES[(i_A>>8)&0x3F]))>>1;

Вот и 4 канала.
 
Понятно что для ШИМ не последнюю роль играют фильтры на выходе сигнала.

На данный момент с ПИКами закончили.
« Последнее редактирование: Июль 19, 2014, 19:30:55 от admin »

Nik83

  • Newbie
  • *
  • Сообщений: 48
    • Просмотр профиля
Re:Формирование сигналов (принцип DDS и PWM)
« Ответ #18 : Май 26, 2011, 17:26:16 »
Классно MIM!! Очень пригодится!

mim

  • Hero Member
  • *****
  • Сообщений: 2700
    • Просмотр профиля
Re:Формирование сигналов (принцип DDS и PWM)
« Ответ #19 : Май 27, 2011, 09:54:15 »
По аналогии рассмотрим и АВР.

Компилятор GCC широко описан на просторах инета. И обладает (на мой взгляд) более расширенными возможностями по сравнению с Boostc.

Мы продолжим разговор о размещении массивов синуса в «Памяти программ».
Для этого в GCC предусмотрено специальное описание массива, для его определения в ROM.

Одномерный
const char SIGNAL [256]  PROGMEM =
{128,131,134,……… ,121,124};


Двумерный
const char SIGNAL [2][256]  PROGMEM =
{
{128,131,134,……… ,121,124},
{128,131,134,……… ,121,124}
};


Для обращения к массиву служит специальная функция, вот так выглядит ее описание:

PORTC = pgm_read_byte(&SIGNAL[N_1]);
или
PORTC = pgm_read_byte(&SIGNAL[N_1][N_2]);

Чтобы это все работало необходимо подключить библиотеку:
#include <avr/pgmspace.h>

Это делается в дополнительном коде, там же определяются и массивы.

Прерывание опишем сами. Такого прерывания Матриксы не предлагают.
Прерывание называется – по совпадению (по сравнению) с регистром OCR0.
Пишется по аналогии с ПИКами. В ДАТЕ на МК посмотрите что значат задействованные регистры.
Вот небольшой пример.
 

Описание МК на русском.
 
« Последнее редактирование: Июль 19, 2014, 19:31:15 от admin »