Давайте попробуем.
Сначала немного теории на пальцах.
Если бы мы точно знали фазу начального сигнала, то интеграл
)
, где A - входной сигнал,

- круговая частота,

- фаза сигнала, был бы равен значению амплитуды полосы спектра во входном сигнале с центральной частотой

и полосой, равной

, где t - время интегрирования.
Но мы не знаем фазу сигнала. Поэтому делается два интегратора
)
)
Второй отличается от первого сдвигом фазы опорного сигнала на 90 градусов.
Если быть точным, то I и Q представляют из себя действительные и мнимые значения результата дискретного преобразования Фурье для выбранной полосы спектра.
Для того, чтобы получить реальную амплитуду необходимо вычислить длинну вектора с координатами I и Q

Для наших целей корень вполне можно опустить. Итого, для определения двух частот необходимо вычислить четыре интеграла
)
)
)
)
А затем, после интегрирования, получить значения мощностей


Далее, банально сравниваем
Код
if ((A50>THRESH)||(A51>THRESH))
{
if (A50>A51)
{
//Есть сигнал с частотой 50кГц
}
else
{
//Есть сигнал с частотой 50кГц
}
}
else
{
//Сигнала нет
}
где THRESH - порог обнаружения.
Теперь, от математики надо перейти к реальной жизни.
Во-первых, определиться с параметрами.
Т.к. полоса обнаружения определяется временем интегрирования, то хорошим выбором будет 1мс. Это эквивалентно полосе в 1кГц. Для простоты реализации на восьмибитных процессорах имеет смысл выбрать частоту дискретизации, ну например, 256кГц (это примерно 5 отсчетов на период сигнала).
Далее, вместо полноценного АЦП будем использовать компаратор. Его можно рассматривать как однобитный АЦП. Так как сигнал имеет разрядность один бит, то разрядность опорного сигнала тоже нужно брать 1 бит (больше просто нет смысла). Причем, этот бит определяет просто знак. Т.е. если бит равен 0 - значение сигнала равно -1, если бит равен 1 - значение сигнала равно +1.
Теперь нарисуем таблицу умножения в наших терминах
Код
a,b,a*b:
-1,-1,+1
+1,-1,-1
-1,+1,-1
+1,+1,+1
и заменим значения уровня сигнала (+1/-1) на битовые значения(1/0)
Код
a,b,a*b:
0,0,1
1,0,0
0,1,0
1,1,1
Если мы внимательно посмотрим на таблицу, то увидим, что a*b=not (a xor

. Т.к. входной сигнал мы можем безболезненно проинвертировать, то not можно убрать. Значит, все умножение входного сигнала на опорный сводится к выполнению операции xor между значениями.
Следовательно, каждый интегратор в коде будет представлять из себя
Код
I+=signal^reference;
, где I - интегратор, signal - выборка сигнала, reference - табличное значение опорного сигнала.
Тут надо обратить внимание на то, что после интегрирования необходимо вычесть из интегратора половину от количества выборок. Происходит это по простой причине - мы вместо прибавления +1 и -1 к интегратору прибавляем либо 1, либо 0, что дает сдвиг на полдиапазона. Либо можно начальное значение интегратора задавать не 0, а минус половина количества выборок.
Теперь про оптимизацию. Если делать интегрирование на каждой выборке - это будет большой перерасход ресурсов. Посему, надо обрабатывать сразу 8 бит.
Для начала - о получении 8ми бит. Надо просто выход компаратора подключить, например, ко входу MOSI, а на вход SCK подать нужную частоту выборок. В результате, в каждом байте, читаемом из SPDR будет получено 8 выборок подряд. Теперь о обработке.
Код
I+=bitcount_table[byte_signal^byte_reference]
, где byte_signal - 8 выборок сигнала в виде одного байта, byte_reference - 8 выборок опорного сигнала в виде одного байта, bitcount_table - таблица длинной 256 байт, каждый байт которой равен количеству единичных бит в индексе.
Теперь попробуем написать код интегратора более близкий к реальности. При этом для простоты разместим 2 таблицы непосредственно в конце флеша, причем, таблица опорных сигналов будет представлять из себя записи по 4 байта, каждый байт - восемь выборок для сигналов I50,Q50,I51,Q51 соответственно, общей длинной ((256 выборок всего)/(8 выборок в байте))*(4 сигнала)=128 байт
Код
typedef unsigned char UREG;
typedef signed char REG;
typedef signed char INT8;
typedef unsigned char UINT8;
volatile UINT8 I50;
volatile UINT8 Q50;
volatile UINT8 I51;
volatile UINT8 Q51;
__flash UINT8 bitcount_table[256] @ FLASHEND-0xFF;
__root __flash UINT8 reference_sig[128] @ FLASHEND-0xFF-0x80;
volatile UINT8 Int_Idx; //Начальное значение - 0x80
__interrupt void ProcessIntegrators(void)
{
UINT8 __flash *r=(UINT8 __flash *)(FLASHEND-0xFF-0x100)+Int_Idx; //Текущее положение в таблице опорных сигналов
UREG sig_i50;
UREG sig_q50;
UREG sig_i51;
UREG sig_q51;
sig_i50=sig_q50=sig_i51=sig_q51=SPDR;
sig_i50^=*r++; //Операция умножения входного сигнала на опорные
sig_q50^=*r++;
sig_i51^=*r++;
sig_q51^=*r++;
Int_Idx=(int)r; //Сохранение указателя
I50+=bitcount_table[sig_i50]; //Собственно интегрирование
Q50+=bitcount_table[sig_q50];
I51+=bitcount_table[sig_i51];
Q51+=bitcount_table[sig_q51];
}
Некоторое колдовство имеется с указателем Int_Idx. Для уменьшения оверхеда его начальное значение должно быть 128, и когда он досчитает до 0, то результат интегрирования будет готов.
Вот результат компиляции EWAVR 5.11
CODE
RSEG NEAR_Z:DATA:NOROOT(0)
REQUIRE `?<Segment init: NEAR_Z>`
I50:
DS 1
Q50:
DS 1
I51:
DS 1
Q51:
DS 1
// 11
// 12 volatile UINT8 Int_Idx; //Начальное значение - 0x80
Int_Idx:
DS 1
// 13
RSEG CODE:CODE:NOROOT(1)
// 14 __interrupt void ProcessIntegrators(void)
ProcessIntegrators:
// 15 {
ST -Y, R31
ST -Y, R30
ST -Y, R22
ST -Y, R21
ST -Y, R20
ST -Y, R19
ST -Y, R18
ST -Y, R17
ST -Y, R16
IN R21, 0x3F
// 16 UINT8 __flash *r=(UINT8 __flash *)(FLASHEND-0xFF-0x100)+Int_Idx; //Текущее положение в таблице опорных сигналов
LDS R16, (I50 + 4)
LDI R31, 62
MOV R30, R16
// 17 UREG sig_i50;
// 18 UREG sig_q50;
// 19 UREG sig_i51;
// 20 UREG sig_q51;
// 21 sig_i50=sig_q50=sig_i51=sig_q51=SPDR;
IN R17, 0x2E
MOV R16, R17
MOV R18, R17
MOV R20, R17
MOV R22, R17
// 22 sig_i50^=*r++; //Операция умножения входного сигнала на опорные
LPM R17, Z+
EOR R22, R17
// 23 sig_q50^=*r++;
LPM R17, Z+
EOR R20, R17
// 24 sig_i51^=*r++;
LPM R17, Z+
EOR R18, R17
// 25 sig_q51^=*r++;
LPM R17, Z+
EOR R16, R17
// 26 Int_Idx=(int)r; //Сохранение указателя
STS (I50 + 4), R30
// 27 I50+=bitcount_table[sig_i50]; //Собственно интегрирование
MOV R30, R22
LDI R31, 63
LPM R17, Z
LDI R30, LOW(I50)
LDI R31, (I50) >> 8
LD R19, Z
ADD R19, R17
ST Z, R19
// 28 Q50+=bitcount_table[sig_q50];
MOV R30, R20
LDI R31, 63
LPM R17, Z
LDI R30, LOW(I50)
LDI R31, (I50) >> 8
LDD R19, Z+1
ADD R19, R17
STD Z+1, R19
// 29 I51+=bitcount_table[sig_i51];
MOV R30, R18
LDI R31, 63
LPM R17, Z
LDI R30, LOW(I50)
LDI R31, (I50) >> 8
LDD R18, Z+2
ADD R18, R17
STD Z+2, R18
// 30 Q51+=bitcount_table[sig_q51];
MOV R30, R16
LDI R31, 63
LPM R16, Z
LDI R30, LOW(I50)
LDI R31, (I50) >> 8
LDD R17, Z+3
ADD R17, R16
STD Z+3, R17
// 31 }
OUT 0x3F, R21
LD R16, Y+
LD R17, Y+
LD R18, Y+
LD R19, Y+
LD R20, Y+
LD R21, Y+
LD R22, Y+
LD R30, Y+
LD R31, Y+
RETI
Ну и финальное действие по достижению переменной Int_Idx значения 0 - оно банально:
Код
#define THRESH (20*0x100)
UREG TestIntegrators(void)
{
__disable_interrupt();
REG i50=I50;
REG q50=Q50;
REG i51=I51;
REG q51=Q51;
__enable_interrupt();
unsigned int A50=__multiply_signed(i50,i50)+__multiply_signed(q50,q50);
unsigned int A51=__multiply_signed(i51,i51)+__multiply_signed(q51,q51);
if ((A50>THRESH)||(A51>THRESH))
{
if (A50>A51) return 1;
else return 2;
}
return 0;
}
В качестве упражнения рекомендую сгенерировать самому необходимые таблицы, написать необходимую инициализацию измерения и проверку окончания.
А теперь - ваши вопросы
"Практика выше (теоретического) познания, ибо она имеет не только достоинство всеобщности, но и непосредственной действительности." - В.И. Ленин