Pull to refresh

Comments 11

Такая штука на дифурах неустойчива – амплитуда или падает в ноль, или улетает в бесконечность. Надо или калиброваться (например, в момент перехода "синуса" через ноль задавать косинусу фиксированное значение), или сделать контур с определённой добротностью и возбуждать его периодическим (да хоть прямоугольным) сигналом.

Кстати, удобнее думать не о синусе и косинусе, а о комплексной экспоненте. Тогда и шаг по времени – просто умножение на константу, и калибровка очевидна.

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

Именно это я и делаю). Получилось устойчиво. Впрочем не проверял на очень длинных выборках. Но свертка работает прекрасно. Что касается устойивости, да, возможно ошибки при вычислении конечных разностей приведут к накоплению лишних значений, что эквивалентно энергии. То же самое будет с интегральной ошибкой в любой системе, но если внести небольшую поправку в добротность, то колебания будут очень медленно затухать. Но период синхронизации можно выбрать очень большим и он может быть любым. В обычном случае значения синуса придется интерполировать или просчитывать для каждого отсчета.

В цифровой форме это будет приближенно. Интересно узнать как аппроксимировали дробно-рациональной функцией передаточную функцию КК. Там в z-пространстве иррациональная функция.

Про Cordic снова напишу. Генерирует сразу и синус и косинус.

CORDIC хорош для произвольных углов, а у автора последовательный перебор, тут можно проще, главное погрешность слишком большую не накопить.

Вот именно, не накопить. Может, лучше иметь таблицу со значениями и от одного-двух ближайших уже рассчитывать через ряды? (не простая апроксимация).

У меня есть расчет sin(x), cos(x) на базе ряда Тейлора с точностью около 1LSB 32-битного числа. Используется математика в целых числах на Arm Cortex M. Шесть умножений кажется 32bit x 32bit => 64bit

Понимаю, что таблица + линейная интерполяция может оказаться немного быстрее, однако не уверен что это даст 1LSB.

Я же специально написал, что не простая апроксимация/интерполяция. Возможно, можно использовать тот же ряд Тейлора, но уже с в 2 раза меньшим кол-во операций. Ну и что-то мне подсказывает, что в инете должна быть куча вариантов таких расчётов для разных условий.

В программах БПФ, ЦФ используется табличный способ и тригонометрические формулы Sin, Cos суммы(разности) углов. Всегда использую эти способы в программах цифровой обработки данных особенно на микроконтроллерах.

Какое преимущество имеет Ваш вариант вычисления ?

Есть такая штука как Numerically Controlled Oscillator, NCO. И там есть момент, связанный со спектральной чистотой генерируемого колебания.

Ну алгоритм довольно нестабильный на реальных разрядностях
Вот так выглядят амплитуды при разных способах округления в младших разрядах

Программа на Python
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Drift caused by biased rounding (Q1.15 fixed-point oscillator)
"""

import math
import numpy as np
import matplotlib.pyplot as plt


# ------------------------------------------------------------
#  ПАРАМЕТРЫ ЭКСПЕРИМЕНТА
# ------------------------------------------------------------
Fs      = 1_000.0          # частота дискретизации, Гц
f0      = 5.0              # генерируемая частота, Гц
seconds = 60               # длительность теста, с
N       = int(Fs * seconds)

omega   = 2 * math.pi * f0 / Fs
kappa_f = 2 * math.sin(omega / 2)     # float-коэффициент

Q   = 15                 # формат Q1.15
S   = 1 << Q             # масштаб 2^Q
kappa = int(round(kappa_f * S))       # фикс-пойнт коэффициент


# ------------------------------------------------------------
#  ВСПОМОГАТЕЛЬНЫЕ ФУНКЦИИ
# ------------------------------------------------------------
def one_step(c: int, s: int, rounding: str = 'trunc'):
    """
    Один рекурсивный шаг генератора:
    c_{n+1} = c_n - kappa*s_n
    s_{n+1} = s_n + kappa*c_{n+1}

    rounding:
        'trunc' – обычный сдвиг вправо (предвзятое округление)
        'sym'   – симметричное округление к ближайшему
    """
    if rounding == 'trunc':
        prod1 = (kappa * s) >> Q
    else:                           # symmetric
        tmp = kappa * s
        prod1 = (tmp + (1 << (Q-1))) >> Q if tmp >= 0 else (tmp - (1 << (Q-1))) >> Q

    c_next = c - prod1

    if rounding == 'trunc':
        prod2 = (kappa * c_next) >> Q
    else:
        tmp = kappa * c_next
        prod2 = (tmp + (1 << (Q-1))) >> Q if tmp >= 0 else (tmp - (1 << (Q-1))) >> Q

    s_next = s + prod2
    return c_next, s_next


def run(rounding: str):
    """Запускаем генератор и возвращаем массив амплитуд"""
    c, s = S, 0    # cos(0)=1, sin(0)=0 в Q1.15
    amp   = np.empty(N)
    for n in range(N):
        amp[n] = math.hypot(c / S, s / S)   # мгновенная амплитуда
        c, s   = one_step(c, s, rounding)
    return amp


# ------------------------------------------------------------
#  МОДЕЛИРУЕМ ОБЕ СХЕМЫ И РИСУЕМ
# ------------------------------------------------------------
amp_trunc = run('trunc')
amp_round = run('sym')

t = np.arange(N) / Fs

plt.figure(figsize=(10, 6))
plt.plot(t, amp_trunc, label='truncate-shift', lw=0.8, color='orange')
plt.plot(t, amp_round, label='symmetric-round', lw=0.8, color='orangered')
plt.title('Drift caused by biased rounding (Q1.15)')
plt.xlabel('Time [s]')
plt.ylabel('Instant amplitude')
plt.grid(True, alpha=0.3)
plt.legend()
plt.tight_layout()
plt.show()

Если нужна квадратурная детекция частот, то напрямую использовть Goertzel гораздо практичнее.

Sign up to leave a comment.

Articles