18е. DDS-генератор AD9833 по SPI: фазовый аккумулятор, частота и фаза, синус/треугольник/меандр, свип и FSK
Как устроен DDS изнутри — фазовый аккумулятор, таблица синуса, помехи усечения фазы и образы около частоты отсчётов, — разобрано в разделах 36к.2 и 36к.4, а генератор на ЦАП самого STM32 — в 15.4 и 36к.8. Здесь — готовая микросхема DDS на маленьком модуле: AD9833 с кварцевым генератором на 25 МГц. Микроконтроллер только пишет ей слова по SPI: частоту, фазу, форму сигнала. Разберём, какие слова и в каком порядке, как менять частоту без скачка фазы, как сделать свип и FSK, что видно в спектре и что ставить после выхода. SPI — в разделе 18, его режимы — в 18.2.
Опоры: RM0008 (DocID13902 Rev 17): APB2 — до 72 МГц, APB1 — до 36 МГц,
с. 48; бит SPI1EN, с. 112; TIM2EN, с. 116; выводы SPI — таблица 25,
с. 166: SCK и MOSI ведущего — альтернативная функция, NSS при программном
управлении — обычный вывод; без ремапа SPI1 — PA4–PA7, с. 185; аппаратный
NSS ведущего держится внизу, пока SPI включён, с. 703; бит DFF — кадр
16 бит, с. 742; BR[2:0], с. 743; CPOL и CPHA, с. 744; флаг BSY,
с. 745; флаг TXE, с. 746. DocID13587 Rev 17 (STM32F103x8/xB): PA4–PA7 —
без допуска 5 В, с. 29. Документа на AD9833 в проекте нет и драйвера на
диске тоже: биты слов, порядок запуска, режим SPI, уровни на выходе взяты
по документации производителя (Analog Devices) и не проверены. Все числа
ниже — прогон драйвера _verify/dds18g/ad18g.c на компьютере (gcc 12.2.0)
со своей моделью AD9833 на Python 3.11; прошивка собрана armclang 6.24 под
Cortex-M3.
Как это называется в документации
Оригинал Перевод DDS — direct digital synthesis прямой цифровой синтез phase accumulator фазовый аккумулятор MCLK — master clock тактовая частота микросхемы FREQREG, PHASEREG слово частоты, слово фазы FSYNC выбор микросхемы, активный ноль SFDR — spurious-free dynamic range запас до худшей помехи dBc дБн — относительно самого сигнала image образ — повтор сигнала около MCLK sweep свип, качание частоты FSK / PSK частотная / фазовая манипуляция
18е.1 Принцип: аккумулятор 28 бит, таблица и ЦАП 10 бит
Внутри AD9833 — та же схема, что в 36к.2, только в кремнии (по документации производителя, не проверено): аккумулятор фазы на 28 бит каждый такт MCLK прибавляет слово частоты FREQREG; старшие 12 бит фазы выбирают точку таблицы синуса; точка уходит в ЦАП на 10 бит. Отсюда
f = FREQREG · MCLK / 2^28 FREQREG = round(f · 2^28 / MCLK)
При MCLK = 25 МГц шаг частоты — 25 000 000 / 2^28 = 0,0931 Гц: это и есть «разрешение 0,1 Гц». Первые такты модели при 1 МГц — слово 10737418, номер точки таблицы растёт на 163,84 за такт:
| такт | аккумулятор | точка таблицы | код ЦАП |
|---|---|---|---|
| 0 | 0 | 0 | 512 |
| 1 | 10737418 | 163 | 638 |
| 2 | 21474836 | 327 | 757 |
| 3 | 32212254 | 491 | 861 |
| 4 | 42949672 | 655 | 943 |
| 5 | 53687090 | 819 | 998 |
| 6 | 64424508 | 983 | 1022 |
| 7 | 75161926 | 1146 | 1014 |
Это 25 отсчётов на период: синус на 1 МГц выходит из ЦАП ступеньками, и сглаживает их только фильтр после выхода (18е.4). Таблица модели — коды от 0 до 1023, середина — 512.
Слово частоты драйвер считает в целых числах: частота в миллигерцах, 64-битное деление с округлением. Так прошивке не нужна плавающая точка:
uint32_t ad_freq_word(uint64_t f_mhz, uint32_t mclk_hz)
{
uint64_t den = (uint64_t)mclk_hz * 1000u; /* f в мГц: FREQREG = f · 2^28 / MCLK */
if (f_mhz >= (1ull << 35))
return AD_FREQ_MAX; /* больше 34 ГГц — сдвиг переполнил бы 64 бита */
uint64_t w = ((f_mhz << AD_PHASE_BITS) + den / 2u) / den;
return w > AD_FREQ_MAX ? AD_FREQ_MAX : (uint32_t)w;
}
| заказано | FREQREG | получилось |
|---|---|---|
| 0,1 Гц | 1 | 0,093 Гц |
| 1 Гц | 11 | 1,024 Гц |
| 400 Гц | 4295 (0x10C7) |
400,003 Гц |
| 1 кГц | 10737 | 999,961 Гц |
| 1234,56 Гц | 13256 | 1234,561 Гц |
| 100 кГц | 1073742 | 100 000,016 Гц |
| 1 МГц | 10737418 | 999 999,978 Гц |
| 5 МГц | 53687091 | 4 999 999,981 Гц |
| 12,5 МГц | 134217728 (0x8000000) |
12 500 000 Гц |
На низких частотах шаг заметен: 1 Гц выходит как 1,024 Гц. 12,5 МГц — половина MCLK, предел Найквиста; на деле выше нескольких мегагерц ступеньки ЦАП и образы (18е.4) портят синус сильнее, чем позволяет разрешение.
Фаза — 12 бит, 4096 шагов на оборот, шаг 360 / 4096 = 0,088°. Драйвер считает слово из тысячных долей градуса: 0,1° → 1, 1° → 11, 45° → 512, 90° → 1024, 180° → 2048, 270° → 3072, а 359,999° округляется до полного оборота — 0.
Регистры (по документации производителя, не проверено). Адрес слова —
в старших битах: 00 — управление, 01 — FREQ0, 10 — FREQ1, 110 —
PHASE0, 111 — PHASE1. Биты слова управления, которые использует драйвер:
| бит | имя | что делает |
|---|---|---|
| 13 | B28 | частота — двумя словами подряд: 14 младших бит, потом 14 старших |
| 12 | HLB | при B28 = 0: какую половину несёт слово частоты |
| 11 | FSELECT | выход считает по FREQ1 |
| 10 | PSELECT | к фазе прибавляется PHASE1 |
| 8 | RESET | аккумулятор в нуле, выход стоит |
| 7 | SLEEP1 | MCLK остановлен |
| 6 | SLEEP12 | ЦАП выключен |
| 5 | OPBITEN | на выход — старший бит вместо ЦАП |
| 3 | DIV2 | при OPBITEN: старший бит как есть или делённый на 2 |
| 1 | MODE | треугольник вместо синуса |
18е.2 Подключение к F103 и запуск с RESET
AD9833 только принимает: три провода — SCLK, SDATA и FSYNC, ответа нет. В F103 это SPI1: SCLK — PA5, SDATA — MOSI на PA7, обе ножки — альтернативная функция (таблица 25, с. 166). FSYNC — обычный выход PA4: им драйвер обрамляет посылку сам. Аппаратный NSS не годится: ведущий держит его внизу всё время, пока SPI включён (с. 703), а AD9833 по FSYNC отличает начало слова.
Слово — 16 бит старшим битом вперёд, поэтому DFF = 1 (с. 742). AD9833
берёт бит по спаду SCLK, и SCLK в покое — единица (по документации
производителя, не проверено): это режим 2, CPOL = 1, CPHA = 0
(с. 744; режимы — 18.2). Частота — 72 МГц / 8 = 9 МГц (BR = 010,
с. 743), одно слово идёт 1,778 мкс. PA4–PA7 не выдерживают 5 В (с. 29):
модуль питают от 3,3 В.
void ad_spi16(const uint16_t *w, uint32_t n)
{
FSYNC_LO();
for (uint32_t i = 0; i < n; i++) {
while (!(SPI1->SR & SPI_SR_TXE)) {
}
SPI1->DR = w[i]; /* слова идут подряд: FSYNC внизу всю посылку */
}
while (!(SPI1->SR & SPI_SR_TXE)) {
}
while (SPI1->SR & SPI_SR_BSY) { /* TXE мало: последнее слово ещё выдвигается */
}
FSYNC_HI();
}
Несколько слов подряд под одним FSYNC — можно: AD9833 считает биты по 16
(по документации производителя, не проверено). Поднимать FSYNC — только
после сброса BSY (с. 745), как в 18.4: TXE встаёт, когда последнее
слово ещё в сдвиговом регистре (18е.7).
Запуск. После включения содержимое регистров не определено, и
генератор сразу что-то выдаёт. В модели первая микросхема включилась со
словом управления 1CE2: меандр, остановленный MCLK, выключенный ЦАП. Порядок
запуска (по документации производителя, не проверено) — RESET вверх, слова
частоты и фазы, RESET вниз:
void ad_init(ad_t *a, uint32_t mclk_hz, uint32_t freq_word, uint16_t phase_word, int wave)
{
a->mclk_hz = mclk_hz;
a->ctrl = (uint16_t)(AD_CTRL | AD_B28 | wave_bits(wave));
uint16_t w[5] = {
(uint16_t)(AD_CTRL | AD_B28 | AD_RESET), /* 2100: RESET держит выход, пока пишем */
(uint16_t)(AD_FREQ0 | LO14(freq_word)), /* FREQ0, младшие 14 бит */
(uint16_t)(AD_FREQ0 | HI14(freq_word)), /* FREQ0, старшие 14 бит */
(uint16_t)(AD_PHASE0 | (phase_word & 0x0FFFu)),
a->ctrl, /* RESET = 0: пуск с нулевой фазы */
};
ad_spi16(w, 5);
}
Для 400 Гц это слова 2100 50C7 4000 C000 2000: слово 4295 = 0x10C7
целиком помещается в младшие 14 бит, старшие — нули. Модель приняла их за
5 слов по 1,778 мкс; частота стала 400,003 Гц после третьего слова. Пока
стоит RESET, выход держит код таблицы для PHASE0: до записи PHASE0 там
было случайное значение — код 111, после — середина шкалы, 512. После
снятия RESET синус пошёл с 512, коды от 0 до 1023, а счёт периодов по
переходам через середину дал 400,00 Гц.
RESET нужен не только против мусора: он обнуляет аккумулятор, и фаза начинается с известного места. Две микросхемы от одного генератора 25 МГц, частота 999 450,68 Гц:
| как запущены | сдвиг B относительно A |
|---|---|
| одни и те же слова, без RESET | 293,71° — какой был после включения |
ad_init обеим одной посылкой |
0,00° |
| RESET, у B PHASE0 = 1024, снятие RESET одним словом обеим | 90,00° |
| то же, но RESET снят по очереди, двумя посылками | 176,75° |
Сдвиг считал отдельный расчёт по выходным кодам обеих микросхем (одна гармоника ДПФ на 16 384 отсчётах). В последней строке B стартовала на 44 такта MCLK позже — на одно слово SPI, 1,76 мкс, — и это 633,25° на этой частоте. Генератор I/Q (синус и косинус) получается, только если RESET снимают одним словом с обоими FSYNC внизу.
18е.3 Частота и фаза: два полуслова, B28 и смена без разрыва
Слово частоты — 28 бит, а в слово SPI влезает 14. При B28 = 1 их пишут
двумя словами подряд, младшие первыми, и регистр меняется только после
второго (по документации производителя, не проверено) — так делает
ad_set_freq. При B28 = 0 бит HLB выбирает половину, и она меняется сразу.
Модель: частота 999 000,017 Гц, переход на 1 001 000,032 Гц.
| способ | слов | промежуточная частота | сколько держалась |
|---|---|---|---|
| половинами (B28 = 0, HLB) | 5 | 997 948,274 Гц | 3555,6 нс — два слова |
ad_set_freq (B28 = 1) |
2 | нет | — |
Новые младшие 14 бит со старыми старшими дают третью частоту, которой не заказывали. Для свипа с мелким шагом это всплеск; если младшая половина переходит через границу 14 бит, промах бывает и большим. Половинами писать выгодно только когда старшая половина не меняется: тогда это одно слово.
Без разрыва фазы. Новое слово частоты меняет только скорость, с которой растёт аккумулятор, — сама фаза продолжается с того места, где была. Многие примеры в сети меняют частоту через RESET: RESET вверх, слово, RESET вниз. Модель на 10 кГц сравнила оба пути:
| смена частоты | скачок фазы | выход стоит |
|---|---|---|
ad_set_freq 10 → 12 кГц |
нет | нет |
| RESET, слово, пуск, 12 → 10 кГц | 83,98° — к нулю | 135 тактов, 5,4 мкс |
С RESET выход в одном такте прыгнул на 509 кодов — щелчок в звуке и разрыв на осциллографе. RESET нужен только при запуске и для синхронизации нескольких микросхем.
Фаза меняется так же — слово 110 или 111 с 12 битами. PHASE
прибавляется к старшим битам аккумулятора, поэтому запись PHASE — это
мгновенный скачок фазы на разницу слов.
18е.4 Синус, треугольник, меандр и спектр
Форму выбирают три бита (по документации производителя, не проверено): MODE = 1 — треугольник из тех же 12 бит фазы, OPBITEN = 1 — на выход вместо ЦАП идёт старший бит, DIV2 — он как есть или делённый на 2. Модель на 10 кГц, 1 мс:
| форма | биты | выход | частота | доля единиц |
|---|---|---|---|---|
| синус | — | коды 0…1023, 1024 уровня | 10 000 Гц | — |
| треугольник | MODE | коды 0…1023, 1024 уровня | 10 000 Гц | — |
| меандр | OPBITEN, DIV2 | 0 и 1 | 10 000 Гц | 0,5 |
| меандр / 2 | OPBITEN | 0 и 1 | 5000 Гц | 0,5 |
Меандр — логический уровень, а не выход ЦАП: его размах — питание модуля, а не 0,6 В (по документации производителя, не проверено).
Спектр синуса. Чтобы спектр сошёлся точно на бинах, слово частоты взято кратным 2^12, а запись — 65 536 отсчётов на 25 МГц: тогда сигнал делает ровно m периодов (тот же приём, что в 36к.2). Помехи посчитаны трижды: настоящий тракт модели (12 бит фазы, 10 бит ЦАП), только усечение фазы с точным синусом и только ЦАП 10 бит при полной фазе:
| m | частота | худшая помеха | только фаза 12 бит | только ЦАП 10 бит |
|---|---|---|---|---|
| 1 | 381,470 Гц | −72,19 дБн | −72,19 дБн | −83,18 дБн |
| 101 | 38 528,442 Гц | −72,19 дБн | −72,19 дБн | −83,18 дБн |
| 2621 | 999 832,153 Гц | −72,19 дБн | −72,19 дБн | −83,18 дБн |
| 2624 | 1 000 976,562 Гц | −76,64 дБн | нет | −76,64 дБн |
| 13107 | 4 999 923,706 Гц | −72,19 дБн | −72,19 дБн | −83,18 дБн |
Худшую помеху задаёт усечение фазы до 12 бит, а не ЦАП: −72,19 дБн против −72,24 дБн по оценке −6,02 дБ на бит из 36к.2. При m = 2624 слово кратно 2^16, отброшенные биты всегда нули, усечения нет — остаётся только ЦАП, −76,64 дБн. У m = 7, 1001 и 6553 — те же −72,19 дБн.
Образы. ЦАП держит каждый отсчёт до следующего, и вокруг MCLK появляются повторы сигнала на 25 МГц − f и 25 МГц + f (36к.4). Модель: отсчёты, растянутые ступеньками в 8 раз, и спектр:
| f | на MCLK − f | на MCLK + f | по формуле sinc | спад на f |
|---|---|---|---|---|
| 1 000 976,6 Гц | −27,39 дБн | −28,05 дБн | −27,60 и −28,29 дБн | −0,023 дБ |
| 4 998 779,3 Гц | −11,91 дБн | −15,25 дБн | −12,04 и −15,56 дБн | −0,579 дБ |
На 5 МГц образ на 20 МГц всего на 12 дБ слабее сигнала — без фильтра это не синус. Фильтр Баттерворта со срезом 6 МГц (как собрать — раздел 178):
| порядок | ослабление на 20 МГц | образ в итоге | потеря на 5 МГц |
|---|---|---|---|
| 3 | 31,38 дБ | −42,03 дБн | 1,253 дБ |
| 4 | 41,83 дБ | −52,84 дБн | 0,907 дБ |
| 5 | 52,29 дБ | −63,55 дБн | 0,649 дБ |
| 6 | 62,75 дБ | −74,20 дБн | 0,460 дБ |
| 7 | 73,21 дБ | −84,79 дБн | 0,325 дБ |
Чтобы образ ушёл ниже помех усечения, до 5 МГц нужен фильтр шестого порядка. Если хватает 1 МГц, образ на 24 МГц уже на 27 дБ ниже, и тот же порядок даёт запас намного больше. Как смотреть спектр на БПФ осциллографа и где он врёт — раздел 36л.10.
18е.5 Свип по таймеру и FSK/PSK
Свип. Частоту меняет таймер: TIM2 (TIM2EN, с. 116) раз в 1 мс
вызывает прерывание, обработчик берёт следующее слово и пишет его через
ad_set_freq — два слова SPI, 32 бита, 3,556 мкс на шине:
void TIM2_IRQHandler(void)
{
TIM2->SR = (uint16_t)~TIM_SR_UIF;
if (!ad_sweep_next(&sw)) {
sweep_start();
(void)ad_sweep_next(&sw);
}
ad_set_freq(&gen, 0, sw.word); /* без RESET: фаза продолжается */
}
Линейный свип 1 → 10 кГц за 900 шагов: шаг слова — 107,374, не целый.
Драйвер считает каждое слово от начала, w0 + (w1 − w0) · k / steps, а не
прибавляет шаг: модель прошла от 999,961 до 9999,983 Гц, отклонение от
прямой — не больше 0,0852 Гц, меньше одного шага 0,0931 Гц. Если
прибавлять целый шаг 107, свип закончился бы на 9968,597 Гц.
Логарифмический свип 100 Гц → 100 кГц за 300 шагов, 100 шагов на декаду:
каждое слово больше прошлого в r = 10^0,01 = 1,0232929923 раза. Драйвер
держит слово в Q16 и умножает на r без плавающей точки:
acc += acc · mul / 2^32, где mul = (r − 1) · 2^32 = 100042640.
static uint64_t mul_q32(uint64_t a, uint32_t m) /* a · m / 2^32 без переполнения 64 бит */
{
return (a >> 32) * m + (((a & 0xFFFFFFFFu) * m) >> 32);
}
Модель: от 100,024 до 100 024,0445 Гц, отклонение от 100,024 · r^k — не больше 0,0722 Гц. Начало сдвинуто: 100 Гц — это слово 1074, а шаг 0,0931 Гц на 100 Гц — уже 931 миллионная. Ни одного RESET за оба свипа — фаза непрерывна. Свип с осциллографом и RC-цепью — 36н.3.
FSK. Две частоты лежат в FREQ0 и FREQ1, бит переключает FSELECT — одно
слово управления, 1,778 мкс. Модель: 1200 Гц — «1», 2200 Гц — «0», 1200 бод,
8 бит 10110010. Аккумулятор при переключении не трогается: ни одного
скачка фазы, соседние отсчёты (через 1 мкс) отличаются не больше чем на
8 кодов.
PSK. PHASE0 = 0, PHASE1 = 2048, бит переключает PSELECT. Те же 8 бит дали 5 скачков ровно на 180,0° — по одному на каждую смену бита, — и соседние отсчёты разошлись до 747 кодов. Спектр модуляции на БПФ — 36н.11.
Переключение словом SPI привязано к времени прерывания: бит меняется через 1,778 мкс после начала посылки, и дрожание таймера и задержка входа в прерывание ложатся на границу бита.
18е.6 Выходной каскад и сон
Выход ЦАП AD9833 — около 0,6 В размах и не с нуля, а со смещением (по документации производителя, не проверено). Для генератора на столе после модуля ставят:
- ФНЧ — против образов около 25 МГц (18е.4);
- разделительный конденсатор или вычитание смещения — чтобы синус шёл вокруг нуля;
- усилитель на ОУ — до нужного размаха; полосы ОУ должно хватать на верхнюю частоту с запасом;
- буфер к нагрузке — сам выход микросхемы рассчитан на высокоомный вход; кабель 50 Ом и согласование — 36к.3.
Модель напряжений не считает: числа размаха, смещения и тока нагрузки здесь только по документации и не проверены.
Сон. SLEEP1 останавливает MCLK: аккумулятор стоит, выход замирает на последнем коде. SLEEP12 выключает ЦАП (по документации производителя, не проверено). Модель на 10 кГц: SLEEP1 на 20 мкс — выход стоит на коде 1001 549 тактов; после пробуждения синус отстал на 72,00° — ровно 20 мкс на 10 кГц. SLEEP12 на 10 мкс — 295 тактов выхода нет. Если важна фаза относительно другой микросхемы, после сна их снова запускают вместе через RESET (18е.2).
18е.7 Грабли
Не тот режим SPI. В режимах 0 и 3 данные меняются на том же спаде SCLK, по которому AD9833 их берёт, — какой бит он увидит, решают наносекунды. Модель в таком случае берёт один из двух соседних бит наугад: из 405 слов запуска и 200 смен частоты правильными дошли 22 в режиме 0 и 23 в режиме 3; частота вместо 8362,9973 Гц — 12 183 296,77 Гц и 7093,05 Гц. В режимах 1 и 2 — все 405. Документация рисует SCLK в покое единицей, поэтому режим 2.
FSYNC на каждый байт. Драйвер «под 8 бит» поднимает FSYNC после каждого байта — слово обрывается на 8-м бите и выбрасывается. В модели из 5 слов запуска не дошло ни одного: 10 обрывков, регистры остались случайными. С кадром 8 бит, но FSYNC на всё слово — все 5 приняты.
FSYNC по TXE, а не по BSY. TXE встаёт, когда последнее слово
только ушло в сдвиговый регистр. В модели FSYNC поднялся после первого
бита последнего слова: принято 4 слова из 5, последнее — снятие RESET —
выброшено. Генератор молчит: выход стоит на 512.
Без RESET при старте. Выход сразу после включения — что угодно: частота, форма, сон — случайные (18е.2). И без RESET две микросхемы не синхронизировать: 293,71° вместо 0°.
Разрешение — не точность. 0,0931 Гц — шаг слова, а точность задаёт
генератор 25 МГц на модуле. Модель с кварцем на +30 ppm, 25 000 750 Гц:
заказали 1 МГц — получили 1 000 029,98 Гц, ошибка — 322 шага.
Лечится калибровкой: измерить частоту, посчитать настоящую MCLK и отдать
её в ad_freq_word — та же формула дала 999 999,99 Гц. Допуск генератора
на конкретном модуле не проверен.
Смена частоты через RESET. Скачок фазы и выход, стоящий 5,4 мкс
(18е.3). Для свипа и FSK — только ad_set_freq и FSELECT.
18е.8 Итоги
- f = FREQREG · 25 МГц / 2^28: шаг 0,0931 Гц; 400 Гц — слово 4295, 400,003 Гц; 1 МГц — 10737418.
- Фаза — 12 бит: 90° — 1024, шаг 0,088°.
- SPI1 F103: режим 2, кадр 16 бит, 9 МГц — 1,778 мкс на слово; FSYNC —
свой вывод, поднимать после
BSY. - Запуск:
2100 50C7 4000 C000 2000; RESET даёт известную фазу: две микросхемы — 0,00° и 90,00° для I/Q. - B28: два слова, без промежуточной частоты; половинами — 997 948,274 Гц на 3555,6 нс.
- Смена частоты без RESET — без скачка фазы; через RESET — 83,98° и 5,4 мкс тишины.
- Худшая помеха синуса — −72,19 дБн от усечения фазы; ЦАП 10 бит — −83,18 дБн.
- Образ на 5 МГц — −11,91 дБн; Баттерворт 6-го порядка на 6 МГц — −74,20 дБн.
- Свип 1 → 10 кГц за 900 шагов — ошибка до 0,0852 Гц; 100 Гц → 100 кГц за 300 шагов — до 0,0722 Гц.
- FSK 1200/2200 Гц без скачков фазы; PSK — 180,0°.
Полные исходники
ad18g.h — биты слов AD9833, структуры и интерфейс драйвера и платформы.
/* ad18g.h — раздел 18е: драйвер DDS-генератора AD9833 без регистров STM32.
* Драйвер собирает 16-битные слова и отдаёт их функции платформы ad_spi16(): FSYNC вниз, слова, FSYNC вверх.
* В прошивке fw18g.c её даёт STM32F103 (SPI1, режим 2), в стенде host18g.c — модель model18g.py.
* Биты слов AD9833 — по документации производителя (в проекте её нет, не проверено). */
#ifndef AD18G_H
#define AD18G_H
#include <stdint.h>
/* ── адрес слова: два старших бита (у фазы — три) ────────────────────── */
#define AD_CTRL 0x0000u /* 00 — регистр управления */
#define AD_FREQ0 0x4000u /* 01 — FREQ0, 14 бит данных */
#define AD_FREQ1 0x8000u /* 10 — FREQ1 */
#define AD_PHASE0 0xC000u /* 110 — PHASE0, 12 бит данных */
#define AD_PHASE1 0xE000u /* 111 — PHASE1 */
/* ── биты регистра управления ─────────────────────────────────────────── */
#define AD_B28 (1u << 13) /* частота — двумя словами подряд: 14 младших, потом 14 старших бит */
#define AD_HLB (1u << 12) /* при B28 = 0: 1 — слово несёт старшие 14 бит, 0 — младшие */
#define AD_FSELECT (1u << 11) /* выход считает по FREQ1 */
#define AD_PSELECT (1u << 10) /* к фазе прибавляется PHASE1 */
#define AD_RESET (1u << 8) /* аккумулятор фазы в ноль, выход — середина шкалы */
#define AD_SLEEP1 (1u << 7) /* MCLK остановлен: аккумулятор стоит */
#define AD_SLEEP12 (1u << 6) /* ЦАП выключен */
#define AD_OPBITEN (1u << 5) /* на выход — старший бит, а не ЦАП: меандр */
#define AD_DIV2 (1u << 3) /* при OPBITEN: 1 — старший бит как есть, 0 — он же, делённый на 2 */
#define AD_MODE (1u << 1) /* треугольник вместо синуса (только при OPBITEN = 0) */
#define AD_PHASE_BITS 28u /* разрядность аккумулятора */
#define AD_FREQ_MAX ((1ul << 28) - 1u)
enum { AD_SINE, AD_TRIANGLE, AD_SQUARE, AD_SQUARE_DIV2 };
typedef struct {
uint32_t mclk_hz; /* частота кварцевого генератора модуля: 25 000 000 */
uint16_t ctrl; /* последнее слово управления (без RESET) */
} ad_t;
typedef struct {
uint32_t w0, w1, steps, k; /* начало, конец, число шагов, номер шага */
uint64_t acc; /* логарифмический: слово частоты в Q16 */
uint32_t mul; /* логарифмический: (r - 1) · 2^32, r — отношение соседних шагов */
uint32_t word; /* текущее слово частоты */
uint8_t log;
} ad_sweep_t;
/* ── платформа: прошивка или стенд ────────────────────────────────────── */
void ad_spi16(const uint16_t *w, uint32_t n); /* FSYNC вниз, n слов старшим битом вперёд, FSYNC вверх */
/* ── расчёт слов ──────────────────────────────────────────────────────── */
uint32_t ad_freq_word(uint64_t f_mhz, uint32_t mclk_hz); /* f в мГц -> FREQREG, с округлением */
uint64_t ad_freq_mhz(uint32_t word, uint32_t mclk_hz); /* FREQREG -> f в мГц, с округлением */
uint16_t ad_phase_word(uint32_t mdeg); /* фаза в тысячных градуса -> PHASEREG */
/* ── управление ───────────────────────────────────────────────────────── */
void ad_init(ad_t *a, uint32_t mclk_hz, uint32_t freq_word, uint16_t phase_word, int wave);
void ad_set_freq(ad_t *a, int reg, uint32_t word); /* B28: два слова за одну посылку */
void ad_set_freq_half(ad_t *a, int reg, uint32_t word); /* B28 = 0: младшие и старшие 14 бит отдельно */
void ad_set_phase(ad_t *a, int reg, uint16_t word);
void ad_wave(ad_t *a, int wave);
void ad_select(ad_t *a, int fsel, int psel); /* FSK и PSK: одно слово управления */
void ad_sleep(ad_t *a, uint16_t bits); /* AD_SLEEP1, AD_SLEEP12 или 0 */
void ad_reset_hold(ad_t *a); /* RESET = 1: выход держит середину шкалы */
void ad_run(ad_t *a); /* RESET = 0: пуск с нулевой фазы */
/* ── свип частоты: шаг — по таймеру ───────────────────────────────────── */
void ad_sweep_lin(ad_sweep_t *s, uint32_t w0, uint32_t w1, uint32_t steps);
void ad_sweep_log(ad_sweep_t *s, uint32_t w0, uint32_t mul, uint32_t steps);
int ad_sweep_next(ad_sweep_t *s); /* 1 — новое слово в s->word, 0 — свип закончен */
#endif
ad18g.c — слова частоты и фазы, запуск с RESET, B28, FSK/PSK, сон и свип.
/* ad18g.c — раздел 18е: драйвер AD9833. Только слова и арифметика; шину даёт ad_spi16() платформы. */
#include "ad18g.h"
#define LO14(w) ((uint16_t)((w) & 0x3FFFu))
#define HI14(w) ((uint16_t)(((w) >> 14) & 0x3FFFu))
uint32_t ad_freq_word(uint64_t f_mhz, uint32_t mclk_hz)
{
uint64_t den = (uint64_t)mclk_hz * 1000u; /* f в мГц: FREQREG = f · 2^28 / MCLK */
if (f_mhz >= (1ull << 35))
return AD_FREQ_MAX; /* больше 34 ГГц — сдвиг переполнил бы 64 бита */
uint64_t w = ((f_mhz << AD_PHASE_BITS) + den / 2u) / den;
return w > AD_FREQ_MAX ? AD_FREQ_MAX : (uint32_t)w;
}
uint64_t ad_freq_mhz(uint32_t word, uint32_t mclk_hz)
{
return ((uint64_t)word * mclk_hz * 1000u + (1u << (AD_PHASE_BITS - 1u))) >> AD_PHASE_BITS;
}
uint16_t ad_phase_word(uint32_t mdeg)
{
return (uint16_t)((((uint64_t)mdeg * 4096u + 180000u) / 360000u) & 0x0FFFu); /* 4096 шагов на оборот */
}
static uint16_t wave_bits(int wave)
{
switch (wave) {
case AD_TRIANGLE: return AD_MODE;
case AD_SQUARE: return AD_OPBITEN | AD_DIV2; /* старший бит: частота f */
case AD_SQUARE_DIV2: return AD_OPBITEN; /* старший бит, делённый на 2: f / 2 */
default: return 0;
}
}
void ad_init(ad_t *a, uint32_t mclk_hz, uint32_t freq_word, uint16_t phase_word, int wave)
{
a->mclk_hz = mclk_hz;
a->ctrl = (uint16_t)(AD_CTRL | AD_B28 | wave_bits(wave));
uint16_t w[5] = {
(uint16_t)(AD_CTRL | AD_B28 | AD_RESET), /* 2100: RESET держит выход, пока пишем */
(uint16_t)(AD_FREQ0 | LO14(freq_word)), /* FREQ0, младшие 14 бит */
(uint16_t)(AD_FREQ0 | HI14(freq_word)), /* FREQ0, старшие 14 бит */
(uint16_t)(AD_PHASE0 | (phase_word & 0x0FFFu)),
a->ctrl, /* RESET = 0: пуск с нулевой фазы */
};
ad_spi16(w, 5);
}
void ad_set_freq(ad_t *a, int reg, uint32_t word)
{
uint16_t base = reg ? AD_FREQ1 : AD_FREQ0;
uint16_t w[2] = {(uint16_t)(base | LO14(word)), (uint16_t)(base | HI14(word))};
(void)a; /* B28 уже стоит в слове управления */
ad_spi16(w, 2);
}
void ad_set_freq_half(ad_t *a, int reg, uint32_t word)
{
uint16_t base = reg ? AD_FREQ1 : AD_FREQ0;
uint16_t c = (uint16_t)(a->ctrl & ~AD_B28);
uint16_t w[5] = {
c, /* B28 = 0, HLB = 0: следующее слово — младшие 14 бит */
(uint16_t)(base | LO14(word)),
(uint16_t)(c | AD_HLB), /* HLB = 1: старшие 14 бит */
(uint16_t)(base | HI14(word)),
a->ctrl, /* обратно B28 */
};
ad_spi16(w, 5);
}
void ad_set_phase(ad_t *a, int reg, uint16_t word)
{
uint16_t w = (uint16_t)((reg ? AD_PHASE1 : AD_PHASE0) | (word & 0x0FFFu));
(void)a;
ad_spi16(&w, 1);
}
void ad_wave(ad_t *a, int wave)
{
a->ctrl = (uint16_t)((a->ctrl & ~(AD_MODE | AD_OPBITEN | AD_DIV2)) | wave_bits(wave));
ad_spi16(&a->ctrl, 1);
}
void ad_select(ad_t *a, int fsel, int psel)
{
a->ctrl = (uint16_t)((a->ctrl & ~(AD_FSELECT | AD_PSELECT)) | (fsel ? AD_FSELECT : 0u) | (psel ? AD_PSELECT : 0u));
ad_spi16(&a->ctrl, 1);
}
void ad_sleep(ad_t *a, uint16_t bits)
{
a->ctrl = (uint16_t)((a->ctrl & ~(AD_SLEEP1 | AD_SLEEP12)) | (bits & (AD_SLEEP1 | AD_SLEEP12)));
ad_spi16(&a->ctrl, 1);
}
void ad_reset_hold(ad_t *a)
{
uint16_t w = (uint16_t)(a->ctrl | AD_RESET);
ad_spi16(&w, 1);
}
void ad_run(ad_t *a)
{
ad_spi16(&a->ctrl, 1);
}
/* ── свип ─────────────────────────────────────────────────────────────── */
void ad_sweep_lin(ad_sweep_t *s, uint32_t w0, uint32_t w1, uint32_t steps)
{
s->w0 = w0;
s->w1 = w1;
s->steps = steps;
s->k = 0;
s->log = 0;
s->word = w0;
}
void ad_sweep_log(ad_sweep_t *s, uint32_t w0, uint32_t mul, uint32_t steps)
{
s->w0 = w0;
s->steps = steps;
s->k = 0;
s->log = 1;
s->mul = mul;
s->acc = (uint64_t)w0 << 16;
s->word = w0;
}
static uint64_t mul_q32(uint64_t a, uint32_t m) /* a · m / 2^32 без переполнения 64 бит */
{
return (a >> 32) * m + (((a & 0xFFFFFFFFu) * m) >> 32);
}
int ad_sweep_next(ad_sweep_t *s)
{
if (s->k > s->steps)
return 0;
if (s->log) {
if (s->k)
s->acc += mul_q32(s->acc, s->mul); /* шаг: умножить на r */
s->word = (uint32_t)((s->acc + 0x8000u) >> 16);
} else { /* считать от начала: ошибка не копится */
int64_t d = (int64_t)s->w1 - (int64_t)s->w0;
int64_t num = d * (int64_t)s->k;
int64_t half = (int64_t)(s->steps / 2u);
s->word = (uint32_t)((int64_t)s->w0 + (num >= 0 ? (num + half) : (num - half)) / (int64_t)s->steps);
}
if (s->word > AD_FREQ_MAX)
s->word = AD_FREQ_MAX;
s->k++;
return 1;
}
fw18g.c — прошивка F103: SPI1 в режиме 2, FSYNC на PA4, свип по TIM2.
/* fw18g.c — раздел 18е: DDS-генератор AD9833 на STM32F103 (BluePill), 72 МГц.
* SPI1 без ремапа: PA5 — SCLK, PA7 — SDATA (MOSI); PA4 — FSYNC, обычный выход (ведём сами); MISO не нужен.
* Режим 2 (CPOL = 1, CPHA = 0): SCLK в покое — единица, AD9833 берёт бит по спаду. Кадр 16 бит (DFF),
* 72 МГц / 8 = 9 МГц. Кварцевый генератор модуля — 25 МГц.
* TIM2 — шаг свипа раз в 1 мс: логарифмический 100 Гц → 100 кГц за 300 шагов (FW_SWEEP = 1)
* или линейный 1 → 10 кГц за 900 шагов (FW_SWEEP = 0); в конце свип начинается заново.
* Тактирование до 72 МГц — как в разделе 6 (здесь не повторяется); APB2 = 72 МГц, APB1 = 36 МГц,
* таймеры APB1 — 72 МГц. */
#include "stm32f10x.h"
#include "ad18g.h"
#ifndef FW_SWEEP
#define FW_SWEEP 1
#endif
#define MCLK_HZ 25000000u
#define TIM_HZ 72000000u
#define SPI_BR SPI_CR1_BR_1 /* BR = 010: fPCLK / 8 */
#define LOG_MUL 100042640u /* (10^0,01 - 1) · 2^32: 100 шагов на декаду */
_Static_assert(SPI_BR == 0x10u, "BR[2:0] = 010 — биты 5:3 SPI_CR1");
_Static_assert(SPI_CR1_CPOL == 0x2u && SPI_CR1_CPHA == 0x1u, "CPOL — бит 1, CPHA — бит 0");
_Static_assert(SPI_CR1_DFF == 0x800u, "DFF — бит 11: кадр 16 бит");
_Static_assert(TIM_HZ / 72u / 1000u == 1000u, "TIM2: 72 МГц / 72 / 1000 = 1 кГц");
#define FSYNC_LO() (GPIOA->BRR = 1u << 4)
#define FSYNC_HI() (GPIOA->BSRR = 1u << 4)
void ad_spi16(const uint16_t *w, uint32_t n)
{
FSYNC_LO();
for (uint32_t i = 0; i < n; i++) {
while (!(SPI1->SR & SPI_SR_TXE)) {
}
SPI1->DR = w[i]; /* слова идут подряд: FSYNC внизу всю посылку */
}
while (!(SPI1->SR & SPI_SR_TXE)) {
}
while (SPI1->SR & SPI_SR_BSY) { /* TXE мало: последнее слово ещё выдвигается */
}
FSYNC_HI();
}
static ad_t gen;
static ad_sweep_t sw;
static void sweep_start(void)
{
#if FW_SWEEP
ad_sweep_log(&sw, ad_freq_word(100000u, MCLK_HZ), LOG_MUL, 300);
#else
ad_sweep_lin(&sw, ad_freq_word(1000000u, MCLK_HZ), ad_freq_word(10000000u, MCLK_HZ), 900);
#endif
}
void TIM2_IRQHandler(void)
{
TIM2->SR = (uint16_t)~TIM_SR_UIF;
if (!ad_sweep_next(&sw)) {
sweep_start();
(void)ad_sweep_next(&sw);
}
ad_set_freq(&gen, 0, sw.word); /* без RESET: фаза продолжается */
}
static void board_init(void)
{
RCC->APB2ENR |= RCC_APB2ENR_IOPAEN | RCC_APB2ENR_SPI1EN;
RCC->APB1ENR |= RCC_APB1ENR_TIM2EN;
FSYNC_HI(); /* FSYNC вверх до настройки вывода */
GPIOA->CRL = (GPIOA->CRL & 0x0F00FFFFu)
| 0x3u << 16 /* PA4 — выход: FSYNC */
| 0xBu << 20 /* PA5 — SCLK, альтернативная функция */
| 0xBu << 28; /* PA7 — SDATA */
SPI1->CR1 = SPI_CR1_MSTR | SPI_CR1_CPOL | SPI_CR1_DFF | SPI_BR | SPI_CR1_SSM | SPI_CR1_SSI; /* режим 2 */
SPI1->CR1 |= SPI_CR1_SPE;
TIM2->PSC = 72u - 1u; /* 1 МГц */
TIM2->ARR = 1000u - 1u; /* 1 мс */
TIM2->DIER = TIM_DIER_UIE;
}
int main(void)
{
board_init();
ad_init(&gen, MCLK_HZ, ad_freq_word(1000000u, MCLK_HZ), 0, AD_SINE); /* 1 кГц, пока свип не пошёл */
sweep_start();
NVIC_EnableIRQ(TIM2_IRQn);
TIM2->CR1 = TIM_CR1_CEN;
for (;;)
__WFI();
}
Дальше: раздел 19 «Шина CAN» — интерфейс для связи между платами и узлами.