PDA

Просмотр полной версии : Soft NCO/DDS на микроконтроллере



maxlab
28.11.2013, 00:24
Поиски простого решения для изготовления синтезатора с применением только DIP микросхем привели к вот такому варианту решения (на схеме, которую любезно согласился нарисовать maxlab).
Основная проблема, что имеется в известных решениях на ATINY например, это "щелчки" при перестройке. Пока, как мне известно, от них удалось избавиться только Сергею RA9YTJ - но ценой весьма нетривиальной управляющей программы и уникального протокола взаимодействия с управляющим процессором, для этого выделяется отдельный управляющий сигнал... Об использовании такого синтезатора совместно с программой "синтезатор с хорошим сервисом" мне пришлось отказаться.
И вот что получилось (пока идея, макета нет):
Применятся микроконтроллер ATMEG162, имеющий интерфейс внешней шины. С регистров 74C595, доступных процессору в адресном пространстве, считывается 32-х битное значение инкремента аккумулятора фазы. Цикл программы, включающий в себя ввод данных из регистров, суммирование и выдачу старшего байта аккумулятора фазы в порт выполняется за 15 тактов, что при тактовой частоте 16 МГц, максимально допустимой для этого микроконтроллера, позволяет формировать сигнал с частотой 455 кГц. После пъезофильтра и усиления его можно использовать как опорную частоту петли ФАПЧ. Эту роль поручаем LM7001, так же доступная микросхема в корпусе DIP16. Хотя, для улучшения быстродействия синтезатора лучше применить MC145170 - у которой минимальный делитель в канале опорной частоты не 72, как у LM7001.
Для улучшения спектра предлагаю использовать двухбитный синусный преобразователь, хотя его полезность пока не проверялась. Можно поступить аналогчино тому, как сделал Сергей RA9YTJ.

Есть несколько вопросов к более опытным коллегам, кот имел дело с ассемблерным программированием на ATMEGA:
1) Можно ли оптимизировать данный код, с учётом того, что младший регистр адреса обращения к внешним регистрам не важен?
2) Можно ли оптимизировать до того, что влезет обращение к 256-байтной таблице синусов и выдача получаемого байта в порт (и при этом не вылезти на пределы 15..16 тактов)? Перенос старшего байта значения фазы из R23 в R30 и использование таблицы, выровненной на 256 байт вполне жизненны. R31 инициализируется вне цикла. При этом получается 17 тактов на цикл (результат на скриншоте).
3) Есть ли версии процессоров с внешним интерфейсом, 5 вольт питания, DIP корпусом и более высокой тактовой частотой?
Полезными будут решения с исходным кодом. Так же принимаются варианты на любом другом семействе процессоров (MICROCHIP, CYPRESS, что угодно), если можно поднять скорость выполнения цикла.


ddsloop:
lds r24, 0xfeff ; Регистр 74HС595
lds r25, 0xfdff
lds r26, 0xfbff
lds r27, 0xf7ff ; Регистр 74HC595
add r20, r24
adc r21, r25
adc r22, r26
adc r23, r27
out 0x12, r23 ; PORTD
rjmp ddsloop

ps: сообщение по просьбе Genadi Zawidowski создавал maxlab - браузер MS IE11 казался строптивым...

PS2: А вот ещё... Если сделать 24бита DDS (c внутренней таблицей синуса) то цикл выполняется за 14 тактов. При этом разрешающая способность на 455 кГц при 16 МГц на ATMEGA162 будет 0.068 герца - примерно 7 сотых.
На 4.5 МГц разрешающая способность после умножения будет 7 десятых герца (примерно один герц). Это приёмники вроде KARLSON. Даже на 45 МГц для up conversion - 7 герц (на верхнем участке - около 13 герц. Т.е., минимальные для простого аппарата 50 или 25 герц шага получаются с запасом.

Genadi Zawidowski
28.11.2013, 04:13
И ещё - удалось получить выдачу преобразованного по таблице значения для ЦАП раз в 9 (девять) тактов. 32 бита FTW. Если выдавать просто старший байт фазового аккумулятора - то раз в 7 (семь) тактов.
Внутренняя частота soft NCO при этом примерно 2.28 МГц, soft DDS 1.77 МГц.
Если интересно - вот как это сделано:


ca: 08 0f add r16, r24
cc: 19 1f adc r17, r25
ce: 2a 1f adc r18, r26
d0: eb 1f adc r30, r27
d2: 00 80 ld r0, Z
d4: 02 ba out 0x12, r0 ; 18
d6: 40 91 00 0e lds r20, 0x0E00
da: 08 0f add r16, r24
dc: 19 1f adc r17, r25
de: 2a 1f adc r18, r26
e0: eb 1f adc r30, r27
e2: 00 80 ld r0, Z
e4: 02 ba out 0x12, r0 ; 18
e6: 50 91 00 0d lds r21, 0x0D00
ea: 08 0f add r16, r24
ec: 19 1f adc r17, r25
ee: 2a 1f adc r18, r26
f0: eb 1f adc r30, r27
f2: 00 80 ld r0, Z
f4: 02 ba out 0x12, r0 ; 18
f6: 60 91 00 0b lds r22, 0x0B00
fa: 08 0f add r16, r24
fc: 19 1f adc r17, r25
fe: 2a 1f adc r18, r26
100: eb 1f adc r30, r27
102: 00 80 ld r0, Z
104: 02 ba out 0x12, r0 ; 18
106: 70 91 00 07 lds r23, 0x0700
10a: 04 0f add r16, r20
10c: 15 1f adc r17, r21
10e: 26 1f adc r18, r22
110: e7 1f adc r30, r23
112: 00 80 ld r0, Z
114: 02 ba out 0x12, r0 ; 18
116: 80 91 00 0e lds r24, 0x0E00
11a: 04 0f add r16, r20
11c: 15 1f adc r17, r21
11e: 26 1f adc r18, r22
120: e7 1f adc r30, r23
122: 00 80 ld r0, Z
124: 02 ba out 0x12, r0 ; 18
126: 90 91 00 0d lds r25, 0x0D00
12a: 04 0f add r16, r20
12c: 15 1f adc r17, r21
12e: 26 1f adc r18, r22
130: e7 1f adc r30, r23
132: 00 80 ld r0, Z
134: 02 ba out 0x12, r0 ; 18
136: a0 91 00 0b lds r26, 0x0B00
13a: 04 0f add r16, r20
13c: 15 1f adc r17, r21
13e: 26 1f adc r18, r22
140: e7 1f adc r30, r23
142: 00 80 ld r0, Z
144: 02 ba out 0x12, r0 ; 18
146: b0 91 00 07 lds r27, 0x0700
14a: 04 0f add r16, r20
14c: 15 1f adc r17, r21
14e: 26 1f adc r18, r22
150: e7 1f adc r30, r23
152: 00 80 ld r0, Z
154: 02 ba out 0x12, r0 ; 18
156: b9 cf rjmp .-142 ; 0xca <main+0x2c>




Уменьшение разрядности накопителя фазы до 24 бит позволит выиграть ещё один такт в каждом из вариантов (NCO/DDS).
upd: ниже выложена обновлённая прошивка. Выдача CS на регистр и ввод из него не работают, пришлось разносить эти команды.

maxlab
28.11.2013, 07:50
Кстати, чип PLL MC145170, который снят с производства фирмой Motorola, клепают теперь здесь под немного другим партнумбером!
http://www.lansdale.com/part_search.php?sear ch=ML145170

Genadi Zawidowski
28.11.2013, 12:44
Ещё мысль - урезав до 24 бит, можно запустить на серии atmega324/atmega644 - эти процессоры 20 мегагерц тянут, разводка как atmega32/16. три регистра повесить прямо на порты, с четвертого снимать сигналы на r-2r. будет цикл короче на такт. т.е., при 20 МГц - 2.5 МГц тактовой DDS.
на один такт короче за счёт укороченного суммирования. Ещё один такт освобождается - команда IN короче, чем чтение из внешней памяти (регистров) - но этот такт нельзя использовать - переход, зацикливающий повторяющиеся операции, занимает два такта...

RA9YTJ
28.11.2013, 14:47
А еще можно пробовать ARM (stm32f100) у них частота 24МГц (можно задать кварцем без PLL) и разрядность 32бита одной командой, а еще есть весьма не плохой ЦАП 12бит на борту(причем 2), памяти много для таблицы, правда корпус не DIP.

Genadi Zawidowski
28.11.2013, 14:51
"по условиям задачи" DIP. Без DIP и счпециальнозаточенны й чип поставить можно. Помните "манифест (http://www.cqham.ru/forum/showthread.php?t=260 91)" RU3AEP? Вот для таких случаев... Когда "что угодно, только не мелкие микросхемы".

3) По возможности хотелось бы избежать применение совсем уж мелконогих микросхем (как не представляется, при такой архитектуре всякие AD99xx не должны понадобиться).

Genadi Zawidowski
28.11.2013, 18:40
Ну вот... Можно применить процессор типа ATMEGA164/ATMEGA324/ATMEGA644 с тактовой частотой 20 МГц.
Схема не отличается. Программа - переделана на работу с портами.
При тактовой 20 МГц soft NCO считает со скоростью 2.85 мегасемпла в секунду, soft DDS - 2.22 мегасемпла в секунду.
К сожалению, на ATMEGA88/AMEGA168/ATMEGA328 программа переносится с некоторыми потерями - при подключении внешнего кварца на микросхеме остаётся только один 8 разрядный порт целиком - а требуется два.
Возможно только изготовление 30-битного soft NCO - порт B принимает данные с 74HC595, на PD0..PD3 подключаются OE 74HC595, старшие выходы NCO доступны на PC5..PC0. Или 6-бит DAC на этих же выводах при 32-bit DDS.

maxlab
29.11.2013, 09:47
Вот моя лабораторная работа. Базируется на PIC18F252. Компилятор CCS PICC

#include <18F252.h>
#device PASS_STRINGS=IN_RAM
#include <string.h>
#fuses H4,NOWDT,NOPUT
#use delay (clock=40000000)
#USE FAST_IO (C)
#USE FAST_IO (B)
#USE FAST_IO (A)
unsigned char cos_table[]={254,254,254,254,25 3,253,253,252,252,25 1,250,249,248,248,24 7,245,244,243,242,24 0,
239,237,236,234,232, 231,229,227,225,223, 221,219,217,214,212, 210,207,205,202,200,
197,195,192,189,186, 184,181,178,175,172, 169,166,163,160,157, 154,151,148,145,142,
139,136,132,129,126, 123,120,117,114,111, 107,104,101,98,95,92 ,89,86,83,80,77,75,7 2,
69,66,63,61,58,56,53 ,50,48,46,43,41,39,3 6,34,32,30,28,26,24, 22,21,19,17,16,14,13 ,12,
10,9,8,7,6,5,4,3,3,2 ,2,1,1,0,0,0,0,0,0,0 ,0,1,1,2,2,3,3,4,5,6 ,7,8,9,10,12,13,14,1 6,17,19,
21,22,24,26,28,30,32 ,34,36,39,41,43,46,4 8,51,53,56,58,61,64, 66,69,72,75,78,80,83 ,86,
89,92,95,98,101,105, 108,111,114,117,120, 123,126,129,133,136, 139,142,145,148,151,
154,157,160,163,166, 169,172,175,178,181, 184,186,189,192,195, 197,200,202,205,207,
210,212,214,217,219, 221,223,225,227,229, 231,233,234,236,237, 239,240,242,243,244,
245,247,248,249,249, 250,251,252,252,253, 253,253,254,254,254, 254};
union
{
unsigned int32 delta;
char data[4];
} ftw;
void main() {
set_tris_c(0);
set_tris_b(0xFF);
set_tris_a(0);
setup_adc_ports(NO_A NALOGS);
static unsigned int32 accum = 0;
while(TRUE){
accum+=ftw.delta;
output_c(cos_table[accum>>24]);
output_a(0b1110);
delay_cycles( 1 );
ftw.data[0]=input_b();
output_a(0b1101);
delay_cycles( 1 );
ftw.data[1]=input_b();
output_a(0b1011);
delay_cycles( 1 );
ftw.data[2]=input_b();
output_a(0b0111);
delay_cycles( 1 );
ftw.data[3]=input_b();
}
}


То что показывает частотомер, только на двух первых картинках более менее правдоподобно. В любом случае частоту генерации можно оценить по данным развертки. На 3 картинке дрожание фронтов достигает 25% от периода. Процессор производительностью 10MIPS. Если применить чип пожирней... например PIC24HJ... с производительностью 40MIPS будет интересней.

P/S Это симуляция в протеус. В железе на выходных попробую

maxlab
29.11.2013, 11:05
Методика Геннадия для синтезирования сигнала, адаптированная для PIC контроллера, дает ощутимый прирост производительности при тех же самых значениях ftw. На 3 картинке также наблюдается дрожание в 25% от периода.



#include <18F252.h>
#device PASS_STRINGS=IN_RAM
#include <string.h>
#fuses H4,NOWDT,NOPUT
#use delay (clock=40000000)
#USE FAST_IO (C)
#USE FAST_IO (B)
#USE FAST_IO (A)
unsigned char cos_table []={254,254,254,254,25 3,253,253,252,252,25 1,250,249,248,248,24 7,245,244,243,242,24 0,
239,237,236,234,232, 231,229,227,225,223, 221,219,217,214,212, 210,207,205,202,200,
197,195,192,189,186, 184,181,178,175,172, 169,166,163,160,157, 154,151,148,145,142,
139,136,132,129,126, 123,120,117,114,111, 107,104,101,98,95,92 ,89,86,83,80,77,75,7 2,
69,66,63,61,58,56,53 ,50,48,46,43,41,39,3 6,34,32,30,28,26,24, 22,21,19,17,16,14,13 ,12,
10,9,8,7,6,5,4,3,3,2 ,2,1,1,0,0,0,0,0,0,0 ,0,1,1,2,2,3,3,4,5,6 ,7,8,9,10,12,13,14,1 6,17,19,
21,22,24,26,28,30,32 ,34,36,39,41,43,46,4 8,51,53,56,58,61,64, 66,69,72,75,78,80,83 ,86,
89,92,95,98,101,105, 108,111,114,117,120, 123,126,129,133,136, 139,142,145,148,151,
154,157,160,163,166, 169,172,175,178,181, 184,186,189,192,195, 197,200,202,205,207,
210,212,214,217,219, 221,223,225,227,229, 231,233,234,236,237, 239,240,242,243,244,
245,247,248,249,249, 250,251,252,252,253, 253,253,254,254,254, 254};
static union u
{
unsigned int32 v;
char data[4];
};
static union u ftw, a, b;
void main(void)
{
set_tris_c(0);
set_tris_b(0xFF);
set_tris_a(0);
setup_adc_ports (NO_ANALOGS);
while(TRUE)
{
ftw.v += a.v;
output_c(cos_table [ftw.data[3]]);
output_a(0b1110);
ftw.v += a.v;
output_c(cos_table [ftw.data[3]]);
b.data[0] = input_b();
ftw.v += a.v;
output_c(cos_table [ftw.data[3]]);
output_a(0b1101);
ftw.v += a.v;
output_c(cos_table [ftw.data[3]]);
b.data[1] = input_b();
ftw.v += a.v;
output_c(cos_table [ftw.data[3]]);
output_a(0b1011);
ftw.v += a.v;
output_c(cos_table [ftw.data[3]]);
b.data[2] = input_b();
ftw.v += a.v;
output_c(cos_table [ftw.data[3]]);
output_a(0b0111);
ftw.v += a.v;
output_c(cos_table [ftw.data[3]]);
b.data[3] = input_b();
ftw.v += b.v;
output_c(cos_table [ftw.data[3]]);
output_a(0b1110);
ftw.v += b.v;
output_c(cos_table [ftw.data[3]]);
a.data[0] = input_b();
ftw.v += b.v;
output_c(cos_table [ftw.data[3]]);
output_a(0b1101);
ftw.v += b.v;
output_c(cos_table [ftw.data[3]]);
a.data[1] = input_b();
ftw.v += b.v;
output_c(cos_table [ftw.data[3]]);
output_a(0b1011);
ftw.v += b.v;
output_c(cos_table [ftw.data[3]]);
a.data[2] = input_b();
ftw.v += b.v;
output_c(cos_table [ftw.data[3]]);
output_a(0b0111);
ftw.v += b.v;
output_c(cos_table [ftw.data[3]]);
a.data[3] = input_b();
ftw.v += a.v;
output_c(cos_table [ftw.data[3]]);
}
}

ur5fjd
29.11.2013, 11:24
Добрый день. Прошу прощенья, но хочу задать вопрос. Регистры используются для получения кода выходной частоты DDS? Я в свое время использовал в качестве DDS тини2313а (20 МГц) с тактированием от внешнего кварцевого генератора на 25 МГц. Данные о частоте в нее передавал по последовательному порту от основного контроллера, собранного на меге8. Выходная частота была до 600 КГц. Управлял LM7001 с коэффициентом (7200/72). Синтезатор предназначался для SDR приемника.

Genadi Zawidowski
29.11.2013, 11:30
Регистры используются для получения кода выходной частоты DDS?
Да, именно для этого - получение кода от управляющего процессора и избежать "щелчков" при перестройке, вызванных "отвлечением процессора на посторонние дела" из 9-тактового основного цикла. В SDR приёмниках эта проблема не стоит, как я понимаю (мелкий шаг обеспечивается на стороне программы PC).

maxlab
29.11.2013, 11:36
Имхо, применение регистров позволяет унифицировать этот узел в р.любительских применениях, так как не зависит от скорости УСАРТОВ и прочее. Плюс попытка избавиться от щелчков и прочих артефактов при перестройке опорника.

О... опередили :)

pps. Видимо воспрошающий пользуется PowerSDR с CAT управлением.

ur5fjd
29.11.2013, 11:55
Спасибо за ответ. Я пользовался программами HDSDR, WINRAD. Процессор "отвлекается" исключительно по прерыванию, когда приходит код частоты. В этот период времени мы интуитивно готовы к изменению частоты. Не хочу Вас отвлекать ответами мне. Все что я хотел узнать я узнал. 73

maxlab
29.11.2013, 11:55
Хочу забросить мысль... В контексте унификации в синтезаторостроении, наверное имеет смысл рассмотреть программно-аппаратный ДПКД на , каком нибудь, малоножечном микроконтроллере. Куча таймеров и счетчиков на борту с наличием SPI bus плюс ФД(ФК) на рассыпухе, снимает проблему с поиском уникальных, с широкими возможностями, микросхем PLL. Все имхо разумеется

maxlab
29.11.2013, 23:29
Немножко причесал код из сообщения №8. Результат превзошел все ожидания :) Это при ftw=0x1000000. Наше тестовое значение.
См.картину

Genadi Zawidowski
04.12.2013, 01:48
Проверил на макете под управлением программы "синтезатора с хорошим сервисом". Исправленный проект выложен в сообщении №7, там же скриншот с осциллографа выходного сигнала DDS (без ЦАП, только старший разряд данных для АЦП), пропущенный через 6 кГц пъезофильтр на 455 кГц. Гарантируется отсутствие бросков частоты при перестройке. Там же выложены прошивки.

Genadi Zawidowski
15.12.2013, 01:32
Для сравнения - спектры сигналов с выхода NCO и с выхода R2R+ФНЧ, и с выхода 455 кГц фильтра.