Embedded HandbookПриложения
Все разделы › III. Периферия
STM32F103 · уровень средний · 27 мин чтения
Открыть в интерактивном справочнике

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 частотная / фазовая манипуляция

Вверху — подключение модуля AD9833 к STM32F103: SPI1 в режиме 2, SCLK на PA5, SDATA на PA7, FSYNC на PA4 обычным выходом, слово 16 бит старшим битом вперёд, 9 МГц; на модуле генератор 25 МГц, после выхода — ФНЧ против образов около MCLK и усилитель со смещением; выход ЦАП — около 0,6 В размах со смещением по документации производителя, не проверено. Посередине — путь фазы внутри AD9833: FREQ0 или FREQ1 по биту FSELECT, аккумулятор 28 бит прибавляет слово частоты каждый такт, старшие 12 бит плюс PHASE0 или PHASE1 по биту PSELECT, таблица синуса на 4096 точек, ЦАП 10 бит, выход VOUT; f = FREQREG · MCLK / 2^28, шаг 0,0931 Гц при 25 МГц; MODE даёт треугольник, OPBITEN — старший бит на выход, DIV2 — частота f или f / 2. Внизу — слова SPI: 00 — управление, 01 и 10 — FREQ0 и FREQ1 по 14 бит, 110 и 111 — PHASE0 и PHASE1 по 12 бит; запуск 2100 50C7 4000 C000 2000 — RESET вверх, FREQ0 двумя словами, PHASE0, RESET вниз; слово 4295 даёт 400,003 Гц


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 В размах и не с нуля, а со смещением (по документации производителя, не проверено). Для генератора на столе после модуля ставят:

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

Сон. 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 Итоги

Полные исходники

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» — интерфейс для связи между платами и узлами.

Открыть в интерактивном справочнике