Конструкция выходного дня: простой спектранализатор 480 полос 60 дБ на stm32f103 и ILI9481

andriano
andriano аватар
Offline
Зарегистрирован: 20.06.2015

Собственно возникла идея собрать спектранализатор звуковой частоты на популярном дисплейчике 320х480.

Т.к. с stm32 знаком меньше месяца, особо задействовать его возможности не стал: все делается программно и последовательно, в том числе и частота дискретизации 48 кГц (точнее, 47.6 кГц - период 21 мкс).

FFT выполняется в плавающей точке, но т.к. занимает около 60 мс, т.е. порядка половины всего последовательного цикла, я решил пока ничего не делать.

Кстати, частота обновления экрана составляет примерно 8.5 Гц.

#define LCD_RD              0x8000 // PB15   - не используется (если использовать, нужно согласовать уровни сигналов)
#define LCD_WR              0x4000 // PB14   
#define LCD_RS              0x2000 // PB13   
#define LCD_CS              0x1000 // PB12          
#define LCD_RESET           0x0800 // PB11   
#define DATA_MASK           0x07f8 // PB3-PB10
#define CTRL_MASK_DEFAULT   0xf800 // биты PB0-PB2 не используется, поэтому не 0xf107: все управляющие биты HIGH
#define CTRL_MASK_WR_RS_SC  0x8803 // маска для записи команд
#define CTRL_MASK_WR_SC     0xa803 // маска для записи данных
#define AUDIO_INPUT         PA0    // аналоговый вход

void Lcd_Write_Com(unsigned char d)  // вывод команды: опустить PB13, вывести байт, поднять PB13
{   
  GPIOB_BASE->ODR = CTRL_MASK_WR_RS_SC | (d << 3);
  GPIOB_BASE->BSRR = LCD_WR;
}

inline void Lcd_Write_Data(unsigned char d)
{
  GPIOB_BASE->ODR = CTRL_MASK_WR_SC | (d << 3);
  GPIOB_BASE->BSRR = LCD_WR;
}

inline void Address_set(uint32_t x1, uint32_t y1, uint32_t x2, uint32_t y2) // позиционирование на экране (оптимизировано)
{
  GPIOB_BASE->ODR = 0x8953; // команда 0x2a - Column Address Set
  GPIOB_BASE->BSRR = LCD_WR;

  GPIOB_BASE->ODR = CTRL_MASK_WR_SC | ((x1 & 0xff00)>>5); //
  GPIOB_BASE->BSRR = LCD_WR;

  GPIOB_BASE->ODR = CTRL_MASK_WR_SC | ((x1 & 0xff) << 3); //
  GPIOB_BASE->BSRR = LCD_WR;

  GPIOB_BASE->ODR = CTRL_MASK_WR_SC | ((x2 & 0xff00)>>5); //
  GPIOB_BASE->BSRR = LCD_WR;

  GPIOB_BASE->ODR = CTRL_MASK_WR_SC | ((x2 & 0xff) << 3); //
  GPIOB_BASE->BSRR = LCD_WR;

  GPIOB_BASE->ODR = 0x895b; // команда 0x2b - Page Address Set
  GPIOB_BASE->BSRR = LCD_WR;

  GPIOB_BASE->ODR = CTRL_MASK_WR_SC | ((y1 & 0xff00)>>5); //
  GPIOB_BASE->BSRR = LCD_WR;

  GPIOB_BASE->ODR = CTRL_MASK_WR_SC | ((y1 & 0xff) << 3); //
  GPIOB_BASE->BSRR = LCD_WR;

  GPIOB_BASE->ODR = CTRL_MASK_WR_SC | ((y2 & 0xff00)>>5); //
  GPIOB_BASE->BSRR = LCD_WR;

  GPIOB_BASE->ODR = CTRL_MASK_WR_SC | ((y2 & 0xff) << 3); //
  GPIOB_BASE->BSRR = LCD_WR;

  GPIOB_BASE->ODR = 0x8963; // команда 0x2c - Mempry Write
  GPIOB_BASE->BSRR = LCD_WR;
}

void Lcd_Init(void) // ILI9481 вызываем один раз, поэтому по скорости не оптимизируем
{
  GPIOB_BASE->BSRR = LCD_RESET;
  delay(5); 
  GPIOB_BASE->BRR = LCD_RESET;
  delay(15);
  GPIOB_BASE->BSRR = LCD_RESET;
  delay(15);

  Lcd_Write_Com(0x11);
  delay(20);
  Lcd_Write_Com(0xD0);
  Lcd_Write_Data(0x07);
  Lcd_Write_Data(0x42);
  Lcd_Write_Data(0x18);
  
  Lcd_Write_Com(0xD1);
  Lcd_Write_Data(0x00);
  Lcd_Write_Data(0x07);
  Lcd_Write_Data(0x10);
  
  Lcd_Write_Com(0xD2);
  Lcd_Write_Data(0x01);
  Lcd_Write_Data(0x02);
  
  Lcd_Write_Com(0xC0);
  Lcd_Write_Data(0x10);
  Lcd_Write_Data(0x3B);
  Lcd_Write_Data(0x00);
  Lcd_Write_Data(0x02);
  Lcd_Write_Data(0x11);
  
  Lcd_Write_Com(0xC5);
  Lcd_Write_Data(0x03);
  Lcd_Write_Com(0x36);
  Lcd_Write_Data(0x0A);
  
  Lcd_Write_Com(0x3A);
  Lcd_Write_Data(0x55); 
  delay(120); 
				
  Lcd_Write_Com(0x29);    //Display ON
  Lcd_Write_Com(0x2c);    // Memory Write
}

void H_line(uint32_t x, uint32_t y, uint32_t l, uint32_t c)                   
{	
  Address_set(x, y, x+l-1, y);
  for(uint32_t i=0; i<l; i++)
  {
      GPIOB_BASE->ODR = CTRL_MASK_WR_SC | ((c >> 5) & DATA_MASK);
      GPIOB_BASE->BSRR = LCD_WR;
      GPIOB_BASE->ODR = CTRL_MASK_WR_SC | ((c << 3) & DATA_MASK);
      GPIOB_BASE->BSRR = LCD_WR;
  }
}

#define size 1024
float x[size], y[size]; // действительные и мнимые части.
float w[size]; // окно

inline void FFT() // 1024 отсчета
{
    int32_t i,i1,j,k,l,l1,l2;
    float c1,c2,tx,ty,t1,t2,u1,u2,z;

    /* Do the bit reversal */
    j = 0;
    for (i = 0; i < 1023; i++) {
        if (i < j) {
            tx = x[i];
            ty = y[i];
            x[i] = x[j];
            y[i] = y[j];
            x[j] = tx;
            y[j] = ty;
        }
        k = 512;
        while (k <= j) {
            j -= k;
            k >>= 1;
        }
        j += k;
    }

    /* Compute the FFT */
    c1 = -1.0;
    c2 = 0.0;
    l2 = 1;
    for (l = 0; l < 10; l++) {
        l1 = l2;
        l2 <<= 1;
        u1 = 1.0;
        u2 = 0.0;
        for (j = 0; j < l1; j++) {
            for (i = j; i < 1024; i += l2) {
                i1 = i + l1;
                t1 = u1 * x[i1] - u2 * y[i1];
                t2 = u1 * y[i1] + u2 * x[i1];
                x[i1] = x[i] - t1;
                y[i1] = y[i] - t2;
                x[i] += t1;
                y[i] += t2;
            }
            z =  u1 * c1 - u2 * c2;
            u2 = u1 * c2 + u2 * c1;
            u1 = z;
        }
        c2 = -sqrt((1.0 - c1) / 2.0);
        c1 = sqrt((1.0 + c1) / 2.0);
    }

    /* Calculate the absolute value */
        for (i = 0; i < 1024; i++) {
            y[i] = (y[i]*y[i] + x[i]*x[i])/1024;
        }
}

inline int log216(uint32_t a) { // логарифмирование в масштабе 16 единиц на 3 дБ (5.3дБ-1)
  uint32_t i = 0;
  uint32_t mask1 = 0x80000000;
  while((!(a & mask1)) && (i < 27)){ i++;  mask1 >>= 1; }
  return (i*16 + 15 - ((a >> (27-i)) & 0x0000000f));
}

void setup()
{
  GPIOB_BASE->ODR = CTRL_MASK_DEFAULT;  // все управляющие HIGH
  GPIOB_BASE->CRL = 0x33333444;         // пины PB7-PB3 - выход, PB2-PB0 - вход (не исп.)
  GPIOB_BASE->CRH = 0x33333333;         // пины PB15-PB8 - выход
  GPIOB_BASE->ODR = CTRL_MASK_DEFAULT;  // 

  Lcd_Init();

  for(int i = 0; i < size; i++) {
    w[i] = sin((2*PI*i)/size);
  }
}

void loop()
{
  int tm = micros() + 21;
  for(int i = 0; i < size; i++) {
    while(micros() < tm) { ; }
    x[i] = (analogRead(AUDIO_INPUT) - 2048)*w[i];
    y[i] = 0;
    tm += 21;
  } 
  FFT(); 
  for(int i = 1; i < 481; i++){
    uint32_t s = log216((uint32_t)(y[i] + y[1024-i])) - 74;
    if(s < 0) s = 0;
    if(s > 319) s = 319;
    H_line(0, i-1, s, 0x001f);
    H_line(s, i-1, 320-s, 0xf800);
  }
}

Кстати, на экране отчетливо видна вторая гармоника амплитудой порядка -40дБ т.е. примерно 1%.

Часть этих искажений дает генератор, а часть - АЦП контроллера. По крайней мере, при уменьшении сигнала на десятки процентов вторая гармоника уменьшается на несколько дБ, но не исчезает совсем. 

В общем, прибор оказался довольно чувствительным.

andriano
andriano аватар
Offline
Зарегистрирован: 20.06.2015

Да, вдогонку: как видно на фото, сигнал на вход подается амплитудой 1.6 В со смещением 1.65 В напрямую.

В случае работы с реальным сигналом, например, с аудиовыхода, естественно, необходимы дополнительные входные цепи.

В простейшем случае - это делитель из двух резисторов по 100 кОм между землей и питанием, середина которого соединена со входом контроллера. А сигнал подается в эту же точку через конденсатор 0.22 мкФ. На всякий случай последовательно с конденсатором можно включить резистор на несколько кОм.

Ну а по хорошему - на входе ФНЧ на ОУ и защита на стабилитроне.

Кстати, фильтр очень желателен: из-за высокого динамического диапазона прекрасно видны все зеркальные частоты от сигнала, спектр которого не ограничен половиной частоты дискретизации. Например, банального меандра.

andriano
andriano аватар
Offline
Зарегистрирован: 20.06.2015
Хотя первоначально этот проект появился как проект выходного дня, результат мне понравился, и я буду доводить его до готового устройства, собранного в корпусе.
 
На сегодняшний момент я вижу развитие проекта так:
1. Переделка аналогового ввода на DMA.
2. Переделка картинки: оцифровка горизонтальной оси, полосатый фон с шагом 3 дБ, более аккуратная реализация логарифма и уточнение масштаба.
3. Сделать органы управления. Вероятно основной - сенсорный экран с меню. Реализовать несколько диапазонов.
4. Дополнить входными цепями с регулировкой чувствительности, ФНЧ, закрытым входом.
5. Корпус.
 
Ввод аналогового сигнала через analogRead() занимает примерно 21 мс с учетом того, что нужно ввести 1024 отсчета с интервалом 21 мкс каждый. Всего полный цикл занимает 118-119 мс, из которых около 60 - FFT. Ну и вывод на экран далеко не бесплатный. Так что радикального повышения FPS ожидать не стоит, но немного выиграть можно.
Ну, кроме того, с DMA как-то разбираться нужно, - вот как раз подходящая задача, чтобы попробовать-пощупать.
Собственно, я впервые столкнулся с stm32 в прошлом месяце. Поэтому в процессе работы сделал для себя немало открытий.
Учитывая, что с одной стороны stm32duino - не совсем тематика форума, а с другой - что среди участников форума интерес к этой теме есть, буду описывать со своей точки зрения, т.е. с точки зрения новичка.
Приведенный ранее вариант программы работал так (в цикле loop()):
1. Ввод данных с АЦП (целые) с массив посредством analogRead() через интервалы времени 21 мкс.
2. Преобразование данных в формат с плавающей точкой.
3. FFT.
4. Преобразование данных для вывода на экран (почти логарифмирование).
5. Вывод на экран.
 
Новый вариант работает так (в цикле loop()):
1. Ожидание окончания работы DMA, запущенного на предыдущем проходе цикла.
2. Переопределение адресов рабочих массивов и перезапуск DMA.
3. Преобразование данных, полученных на предыдущем проходе цикла, в формат с плавающей точкой.
4. FFT.
5. Преобразование данных для вывода на экран (логарифмирование).
6. Вывод на экран.
 
При этом система ADC-DMA работает в фоне на этапах 3-6 и не занимает процессорного времени.
 
Для того чтобы все это работало, необходимо настроить 3 устройства: таймер, АЦП (ADC) и контроллер ПДП (DMA). Таймер работает на частоте дискретизации и при каждом достижении конца цикла выдает сигнал на АЦП, тот, в свою очередь, оцифровывает входной сигнал и по завершении сигнализирует ПДП, который берет с АЦП число и кладет его в очередную ячейку массива.
Длина массива задается в параметрах ПДП. Таймер и АЦП работают постоянно, теряя данные тогда, когда ПДП их вовремя не забирает. Т.е. скажем, раз в 100 мс ПДП включается и переносит в массив данные длиной 21 мс, а остальные 79 мс, хотя и оцифровываются, но контроллером ПДП игнорируются и поэтому пропадают.
В дальнейшем я планирую кроме частоты оцифровки 48 кГц использовать и другие (выбор из меню), в частности 24 и 12 кГц, так что длительность оцифровываемого фрагмента сможет достигать 84 мс, что уже близко к полному циклу обработки (поэтому и понадобился п.1 в новом варианте).
Можно при оцифровке обойтись и без таймера (совсем). Для этого нужно установить режим ADC->CR2.CONT, при котором следующая оцифровка начинается сразу, как заканчивается предыдущая. Если при этом в регистре ADC->SMPR2 установить максимальное время преобразования, то при остальных настройках по умолчанию частота дискретизации получится как раз 48 кГц. Подбирая другие времена оцифровки и другую частоту тактирования АЦП, можно получить другие частоты дискретизации, но сетка этих частот получается гораздо более редкая, чем при использовании таймера, а потому погрешность задания частоты в большинстве случаев окажется значительно выше.
 
Длину периода цикла, таким образом, удалось уменьшить до 99-100 мс и поднять частоту обновления экрана до 10 FPS.
Для отладки на пин PB1 выводится частота дискретизации. В финальной версии, конечно, это нужно будет отключить.
 
#include "dma.h"

#define LCD_RD              0x8000 // PB15   - не используется (если использовать, нужно согласовать уровни сигналов)
#define LCD_WR              0x4000 // PB14   
#define LCD_RS              0x2000 // PB13   
#define LCD_CS              0x1000 // PB12          
#define LCD_RESET           0x0800 // PB11   
#define DATA_MASK           0x07f8 // PB3-PB10
#define CTRL_MASK_DEFAULT   0xf800 // биты PB0-PB2 не используется, поэтому не 0xf107: все управляющие биты HIGH
#define CTRL_MASK_WR_RS_SC  0x8803 // маска для записи команд
#define CTRL_MASK_WR_SC     0xa803 // маска для записи данных
#define AUDIO_INPUT         PA0    // аналоговый вход

void Lcd_Write_Com(unsigned char d)  // вывод команды: опустить PB13, вывести байт, поднять PB13
{   
  GPIOB_BASE->ODR = CTRL_MASK_WR_RS_SC | (d << 3);
  GPIOB_BASE->BSRR = LCD_WR;
}

inline void Lcd_Write_Data(unsigned char d)
{
  GPIOB_BASE->ODR = CTRL_MASK_WR_SC | (d << 3);
  GPIOB_BASE->BSRR = LCD_WR;
}

inline void Address_set(uint32_t x1, uint32_t y1, uint32_t x2, uint32_t y2) // позиционирование на экране (оптимизировано)
{
  GPIOB_BASE->ODR = 0x8953; // команда 0x2a - Column Address Set
  GPIOB_BASE->BSRR = LCD_WR;

  GPIOB_BASE->ODR = CTRL_MASK_WR_SC | ((x1 & 0xff00)>>5); //
  GPIOB_BASE->BSRR = LCD_WR;

  GPIOB_BASE->ODR = CTRL_MASK_WR_SC | ((x1 & 0xff) << 3); //
  GPIOB_BASE->BSRR = LCD_WR;

  GPIOB_BASE->ODR = CTRL_MASK_WR_SC | ((x2 & 0xff00)>>5); //
  GPIOB_BASE->BSRR = LCD_WR;

  GPIOB_BASE->ODR = CTRL_MASK_WR_SC | ((x2 & 0xff) << 3); //
  GPIOB_BASE->BSRR = LCD_WR;

  GPIOB_BASE->ODR = 0x895b; // команда 0x2b - Page Address Set
  GPIOB_BASE->BSRR = LCD_WR;

  GPIOB_BASE->ODR = CTRL_MASK_WR_SC | ((y1 & 0xff00)>>5); //
  GPIOB_BASE->BSRR = LCD_WR;

  GPIOB_BASE->ODR = CTRL_MASK_WR_SC | ((y1 & 0xff) << 3); //
  GPIOB_BASE->BSRR = LCD_WR;

  GPIOB_BASE->ODR = CTRL_MASK_WR_SC | ((y2 & 0xff00)>>5); //
  GPIOB_BASE->BSRR = LCD_WR;

  GPIOB_BASE->ODR = CTRL_MASK_WR_SC | ((y2 & 0xff) << 3); //
  GPIOB_BASE->BSRR = LCD_WR;

  GPIOB_BASE->ODR = 0x8963; // команда 0x2c - Mempry Write
  GPIOB_BASE->BSRR = LCD_WR;
}

void Lcd_Init(void) // ILI9481 вызываем один раз, поэтому по скорости не оптимизируем
{
  GPIOB_BASE->BSRR = LCD_RESET;
  delay(5); 
  GPIOB_BASE->BRR = LCD_RESET;
  delay(15);
  GPIOB_BASE->BSRR = LCD_RESET;
  delay(15);

  Lcd_Write_Com(0x11);
  delay(20);
  Lcd_Write_Com(0xD0);
  Lcd_Write_Data(0x07);
  Lcd_Write_Data(0x42);
  Lcd_Write_Data(0x18);
  
  Lcd_Write_Com(0xD1);
  Lcd_Write_Data(0x00);
  Lcd_Write_Data(0x07);
  Lcd_Write_Data(0x10);
  
  Lcd_Write_Com(0xD2);
  Lcd_Write_Data(0x01);
  Lcd_Write_Data(0x02);
  
  Lcd_Write_Com(0xC0);
  Lcd_Write_Data(0x10);
  Lcd_Write_Data(0x3B);
  Lcd_Write_Data(0x00);
  Lcd_Write_Data(0x02);
  Lcd_Write_Data(0x11);
  
  Lcd_Write_Com(0xC5);
  Lcd_Write_Data(0x03);
  Lcd_Write_Com(0x36);
  Lcd_Write_Data(0x0A);
  
  Lcd_Write_Com(0x3A);
  Lcd_Write_Data(0x55); 
  delay(120); 
				
  Lcd_Write_Com(0x29);    //Display ON
  Lcd_Write_Com(0x2c);    // Memory Write
}

void H_line(uint32_t x, uint32_t y, uint32_t l, uint32_t c)                   
{	
  Address_set(x, y, x+l-1, y);
  for(uint32_t i=0; i<l; i++)
  {
      GPIOB_BASE->ODR = CTRL_MASK_WR_SC | ((c >> 5) & DATA_MASK);
      GPIOB_BASE->BSRR = LCD_WR;
      GPIOB_BASE->ODR = CTRL_MASK_WR_SC | ((c << 3) & DATA_MASK);
      GPIOB_BASE->BSRR = LCD_WR;
  }
}

#define size 1024
float x[size], y[size]; // действительные и мнимые части.
float w[size]; // окно
int16_t ADCBuffer[size], ADCBuffer2[size];
int16_t * buffADC, * buffFFT;

inline void FFT() // 1024 отсчета
{
    int32_t i,i1,j,k,l,l1,l2;
    float c1,c2,tx,ty,t1,t2,u1,u2,z;

    /* Do the bit reversal */
    j = 0;
    for (i = 0; i < 1023; i++) {
        if (i < j) {
            tx = x[i];
            ty = y[i];
            x[i] = x[j];
            y[i] = y[j];
            x[j] = tx;
            y[j] = ty;
        }
        k = 512;
        while (k <= j) {
            j -= k;
            k >>= 1;
        }
        j += k;
    }

    /* Compute the FFT */
    c1 = -1.0;
    c2 = 0.0;
    l2 = 1;
    for (l = 0; l < 10; l++) {
        l1 = l2;
        l2 <<= 1;
        u1 = 1.0;
        u2 = 0.0;
        for (j = 0; j < l1; j++) {
            for (i = j; i < 1024; i += l2) {
                i1 = i + l1;
                t1 = u1 * x[i1] - u2 * y[i1];
                t2 = u1 * y[i1] + u2 * x[i1];
                x[i1] = x[i] - t1;
                y[i1] = y[i] - t2;
                x[i] += t1;
                y[i] += t2;
            }
            z =  u1 * c1 - u2 * c2;
            u2 = u1 * c2 + u2 * c1;
            u1 = z;
        }
        c2 = -sqrt((1.0 - c1) / 2.0);
        c1 = sqrt((1.0 + c1) / 2.0);
    }

    /* Calculate the absolute value */
        for (i = 0; i < 1024; i++) {
            y[i] = (y[i]*y[i] + x[i]*x[i])/1024;
        }
}

inline int log216(uint32_t a) { // логарифмирование в масштабе 16 единиц на 3 дБ (5.3дБ-1)
  uint32_t i = 0;
  uint32_t mask1 = 0x80000000;
  while((!(a & mask1)) && (i < 27)){ i++;  mask1 >>= 1; }
  return (i*16 + 15 - ((a >> (27-i)) & 0x0000000f));
}

void ADC_DMA_init(void)
{
    int a = analogRead(PA0);
    Serial.print("analogRead: ");           Serial.println(a, HEX);
    Serial.print("ADC1_BASE->DR:       ");  Serial.println(ADC1_BASE->DR, HEX);
 
//    GPIOA_BASE->CRL = 0x444444b0;         // пины PA7-PA2 - вход, PA0 - аналоговый вход, PA1 - выход таймера (для отладки)
    RCC_BASE->AHBENR |= 1; //DMA1EN - вкл.тактирования DMA1, все остальное (таймер, порты, ADC, RCC_PCLK2_Div6) stm32duino по умолчанию уже включило
    
// DMA1->CCR1: MEM2MEM=0, PL=2, MSIZE=01, PSIZE=01, MINC=1, PINC=0, CIRC=0, DIR=0, TEIE=0, HTIE=0, TCIE=0, EN=1 
    DMA1_BASE->CCR1 |= 0x2000; // PL=2 (bits 13-12) - high
    DMA1_BASE->CCR1 |= 0x0400; // MSIZE=01 (bits 11-10) - 16 bits
    DMA1_BASE->CCR1 |= 0x0100; // PIZE=01 (bits 9-8) - 16 bits
    DMA1_BASE->CCR1 |= 0x0080; // MINC (bit 7) инкрементировать адреса в памяти
    DMA1_BASE->CNDTR1 = size;  // сколько сэмплов передать (read only when channel disabled)
    DMA1_BASE->CPAR1 = (uint32_t)&ADC1_BASE->DR; // читать из ADC1
    DMA1_BASE->CMAR1 = (uint32_t)buffADC; // куда писать
    
    DMA1_BASE->CCR1 |= 1; // EN (bit 0) 0 включаем

    Serial.print("DMA1_BASE->ISR:      ");  Serial.println(DMA1_BASE->ISR, HEX);
    Serial.print("DMA1_BASE->IFCR:     ");  Serial.println(DMA1_BASE->IFCR, HEX);
    Serial.print("DMA1_BASE->CCR1:     ");  Serial.println(DMA1_BASE->CCR1, HEX);
    Serial.print("DMA1_BASE->CNDTR1:   ");  Serial.println(DMA1_BASE->CNDTR1, HEX);
    Serial.print("DMA1_BASE->CPAR1:    ");  Serial.println(DMA1_BASE->CPAR1, HEX);
    Serial.print("DMA1_BASE->CMAR1:    ");  Serial.println(DMA1_BASE->CMAR1, HEX);
    Serial.print("ADC1_BASE->DR:       ");  Serial.println(ADC1_BASE->DR, HEX);
 
// ADC1->CR1: AWDEN=0, JAWDEN=0, DUALMOD=0(Independed), DISCNUM=0, JDISCEN=0, DISCEN=0, JAUTO=0, AWDSGL=0, SCAN=0, JEOCIE=0, AWDIE=0, EOCIE=0, AWDCH=0
// ADC1->CR2: TSVREFE=0, SWSTART=0, JSWSTART=0, EXTTRIG=1, EXTCEL=3(Tim2-CC2), JEXTTRIG=0, JEXTCEL=0, ALIGN=0, DMA=1, RSTCAL=0, CAL=0, CONT=0, ADON=1
// ADC1->SQR1.l=0 (one channel), SQR3.SQ1=0 (first channel)
    ADC1_BASE->CR2 |= 0x00100000; // EXTTRIG (bit 20)
////////  ADC1_BASE->CR2 |= 0x00000002; // CONT=1 (bit 1) - это непрерывное преобразование без таймера ???
    ADC1_BASE->CR2 &= 0xfff1ffff;
    ADC1_BASE->CR2 |= 0x00060000; // EXTCEL=3 (bit 19-17) Tim2CC2
    ADC1_BASE->CR2 |= 0x00000100; // DMA (bit 8)
    ADC1_BASE->CR2 |= 1; // ADON
 
  Serial.print("ADC1_BASE->SR:       ");  Serial.println(ADC1_BASE->SR, HEX);
  Serial.print("ADC1_BASE->CR1:      ");  Serial.println(ADC1_BASE->CR1, HEX);
  Serial.print("ADC1_BASE->CR2:      ");  Serial.println(ADC1_BASE->CR2, HEX);
  Serial.print("ADC1_BASE->SQR1:     ");  Serial.println(ADC1_BASE->SQR1, HEX);
  Serial.print("ADC1_BASE->SQR2:     ");  Serial.println(ADC1_BASE->SQR2, HEX);
  Serial.print("ADC1_BASE->SQR3:     ");  Serial.println(ADC1_BASE->SQR3, HEX);
  Serial.print("ADC1_BASE->DR:       ");  Serial.println(ADC1_BASE->DR, HEX);

// default by stm32duino: TIMER2->CR1.ARPE=1, CR1.CEN=1, CCMR1/2=[0x68], DMAR=0x81 (?)
// TIMER2->ARR=749, CCR2=374, EGR.CC2G=1, EGR.UG=1 (?), CCER.CC2E=1
    TIMER2_BASE->ARR = 749; // 36000k/750=48k
    TIMER2_BASE->CCR2 = 374; // 750/2=375
    TIMER2_BASE->EGR  = 0x0005;  // не уверен, что это нужно
    TIMER2_BASE->CCER = 0x0010;  // CC2 enable
}

void inline resetDMA(){
  DMA1_BASE->CCR1 &= 0xfffffffe; // EN (bit 0)
  DMA1_BASE->CNDTR1 = 1024;
  DMA1_BASE->IFCR = 0x0000000f;
  int16_t * tmp = buffFFT;
  buffFFT = buffADC;
  buffADC = tmp;
  DMA1_BASE->CMAR1 = (uint32_t)buffADC; // куда писать
  DMA1_BASE->CCR1 |= 1;
}


void setup()
{
  Serial.begin(9600);
  while (!Serial) { ; } // wait for serial port to connect.
  GPIOB_BASE->ODR = CTRL_MASK_DEFAULT;  // все управляющие HIGH
  GPIOB_BASE->CRL = 0x33333444;         // пины PB7-PB3 - выход, PB2-PB0 - вход (не исп.)
  GPIOB_BASE->CRH = 0x33333333;         // пины PB15-PB8 - выход
  GPIOB_BASE->ODR = CTRL_MASK_DEFAULT;  // 
  GPIOA_BASE->CRL = 0x444444b0;         // пины PA7-PA2 - вход (не исп.), PA0 - аналоговый вход, PA1 - выход таймера (для отладки)

  Lcd_Init();
  buffADC = ADCBuffer;
  buffFFT = ADCBuffer2;
  ADC_DMA_init();

  for(int i = 0; i < size; i++) {
    buffADC[i] = 0;
    buffFFT[i] = 0;
//    x[i] = 0;
//    y[i] = 0;
  }

  for(int i = 0; i < size; i++) {
    w[i] = sin((2*PI*i)/size);
  }
}

void loop()
{
  int t0 = millis(); 
/*  int tm = micros() + 21;
  for(int i = 0; i < size; i++) {
    while(micros() < tm) { ; }
    x[i] = (analogRead(AUDIO_INPUT) - 2048)*w[i];
    y[i] = 0;
    tm += 21;
  } */
  while((DMA1_BASE->ISR & 2)== 0) { ; }
  resetDMA();
//  while((DMA1_BASE->ISR & 2)== 0) { ; }
  for(int i = 0; i < size; i++) {
    x[i] = (buffFFT[i] - 2048)*w[i];
    y[i] = 0;
  }
  
  FFT(); 
  for(int i = 1; i < 481; i++){
    uint32_t s = log216((uint32_t)(y[i] + y[1024-i])) - 74;
    if(s < 0) s = 0;
    if(s > 319) s = 319;
    H_line(0, i-1, s, 0x001f);
    H_line(s, i-1, 320-s, 0xf800);
  }
  int t1 = millis();
  Serial.println(t1-t0); 
}

 

 
a5021
Offline
Зарегистрирован: 07.07.2013

А с чем связна такая форма обращения к регистрам: ppp_BASE->rrr ? Обычно все ж пишут DMA1_Channel1->CCR, а не DMA1_BASE->CCR.

andriano
andriano аватар
Offline
Зарегистрирован: 20.06.2015

Ну эту форму не я придумал. Почему-то и GPIO и TIMERn - все задаются через *_BASE. И, кстати, в комплекте Arduino IDE есть файлик dma.h, в котором DMA подключается именно таким образом.

a5021
Offline
Зарегистрирован: 07.07.2013

Я понял -- это старый вариант STM32DUINO. Возможно, имеет смысл задуматься о переходе на новый.

dimax
dimax аватар
Онлайн
Зарегистрирован: 25.12.2013

Я нашёл на форуме упоминание Роджера Кларка (автора основной библиотеки и основателя/модератора Stm32duino.com) про существование других вариантов аддонов под ардуино: https://www.stm32duino.com/viewtopic.php?t=97  В общем он пишет, что этот вариант от самих STM, правда на st.com об этом  репозитории никаких упоминаний не нашёл, т.к. что возможно что это пока делается полуофициально. На форуме stm32duino есть отдельный раздел посвящённый этому ядру http://stm32duino.com/viewforum.php?f=48

Вопрос переходить или нет на новое ядро от stm пока остаётся открытым... :)

andriano
andriano аватар
Offline
Зарегистрирован: 20.06.2015

Честно говоря, я не любитель менять что-то на более новое, пока это "что-то" работает.

Это - первое.

Второе:

Когда искал примеры/аналоги работы с DMA, обнаружил, что гораздо больше исходников, ориентированных на keil (что, впрочем, логично). Но это немного более высокий уровень. Думаю, что с keil рано или поздно познакомиться придется, но для начала лучше разобраться на низком уровне - через регистры - что как раз и дает Ардуино. А, судя по записям в "новом" варианте, там уже обращение идет не к регистрам, а к битовым полям внутри регистра.

a5021
Offline
Зарегистрирован: 07.07.2013

Нет никакой отдельной сущности регистров в виде битовых полей. Разряды регистра и биты этих самыых "полей" -- суть одно и то же. То, что у Кларка может быть записано так:

DMA1_BASE->CCR1 |= 0x2000; // PL=2 (bits 13-12) - high
DMA1_BASE->CCR1 |= 0x0400; // MSIZE=01 (bits 11-10) - 16 bits
DMA1_BASE->CCR1 |= 0x0100; // PIZE=01 (bits 9-8) - 16 bits
DMA1_BASE->CCR1 |= 0x0080; // MINC (bit 7) инкрементировать адреса в памяти

на CMSIS будет выглядеть так:

DMA1_Channel1->CCR = (
  DMA_CCR_PL_1        |  /* Channel Priority level PL = 10 => High  */
  DMA_CCR_MSIZE_0     |  /* Memory size     MSIZE=01 => 16 bits     */
  DMA_CCR_PSIZE_0     |  /* Peripheral size PSIZE=01 => 16 bits     */
  DMA_CCR_MINC        |  /* Memory increment mode                   */
  DMA_CCR_EN             /* Channel enable                          */
);

Никаких волшебных цифр, самодокументируемый, по сути, код. Без проблем можно таскать куски кода между ардуиной и другими средами (кейл, иар и пр.).

Нет смысла набивать руку на кларковских изобретениях, т.к. это дополнительно ничего не дает, но кроме конкретно этой реализации более неприменимо нигде.

dimax
dimax аватар
Онлайн
Зарегистрирован: 25.12.2013

Всё таки из принципа покопался, и  нашёл на st.com ссылку на ардуиновский аддон, она была в мануале к одной из отладочных плат. Поставил аддон, из того что бросилось в глаза -нет загрузки с бутлоадера. :(

 

a5021
Offline
Зарегистрирован: 07.07.2013

А разве "Serial" -- это не оно?
 

dimax
dimax аватар
Онлайн
Зарегистрирован: 25.12.2013

a5021, ну это аппаратный сериал с выходом пинов на плату, то есть требует отдельного переходника.  А в родном аддоне был ещё полусофтовый сериал (USB-CDC), который позволял через usb-разъём прошиваться.

a5021
Offline
Зарегистрирован: 07.07.2013

Я с такими тонкостями дел не имел, т.к. привык для плат без лоадера просто внешний программатор цеплять. С другой стороны, наверняка есть возможность использовать этот USB-CDC и с Arduino_Core_STM32. Те же NUCLEO-64 имеют возможность заливки скетчей вообще через MassStorage, а одна из этих плат, как раз на F103.

andriano
andriano аватар
Offline
Зарегистрирован: 20.06.2015

Ну, раз уж разговор ушел в сторону: никто не имел дела с stm32f407?

Вчера пришла плата. В документации, вроде, написано, что загрузчик для нее в отличие от f103 не нужен, т.к. есть изначально. Но что-то моя Винда говорит - неопознанное устройство. Я так понимаю, нужен драйвер, но драйвер от f103 не подходит, а поиск по и-нету пока результатов не дал.

a5021
Offline
Зарегистрирован: 07.07.2013

Что хоть за плата?

andriano
andriano аватар
Offline
Зарегистрирован: 20.06.2015
a5021
Offline
Зарегистрирован: 07.07.2013

Может оказаться, что только дядюшке Ляо известно о прошивке внутри и том, какие драйвера под это дело потребны.

dimax
dimax аватар
Онлайн
Зарегистрирован: 25.12.2013

andriano, мапловский может не подходить из-за того, что виды-пиды там нестандартные вписаны. Может попробовать родной драйвер?

Andy
Andy аватар
Offline
Зарегистрирован: 01.01.2016

dimax пишет:
Может попробовать родной драйвер?
Это виртуальный COM порт.

Драйвер здесь

b707
Offline
Зарегистрирован: 26.05.2017

ну и моща... почти 200 МГц, полмегабайта памяти, почти сотня GPIO, сеть и прочие интерфейсы на борту.

Куда только все это использовать в таком формате...

Andriano - если есть задумки, подо что брали плату - поделитесь, интересно.

andriano
andriano аватар
Offline
Зарегистрирован: 20.06.2015

1. Увы, ни одна из ссылок не помогла.

2. Просто заинтересовался stm32, захотелось попробовать что-нибудь посерьезнее f103. Тут, вроде, и интерфейс для камеры и FPU, так что для чего-нибудь, думаю, пригодится.

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

 

Может, ее не через USB, а через JTAC/SWD надо подключать?

b707
Offline
Зарегистрирован: 26.05.2017

andriano пишет:

Может, ее не через USB, а через JTAC/SWD надо подключать?

а может не тот USB - в описании говорится, что на плате аж три распаянных разъема. Да еще и посмотрите, как оно распаяно - помните - на F103 надо было резистор напаять, чтобы USB опознался.

andriano
andriano аватар
Offline
Зарегистрирован: 20.06.2015

Наверное пора переходить с 4-е измерение, потому как в этих я двух других USB разъемомв не вижу

У меня на двух компах вполне распознается без дополнительных резисторов все 4 имеющиеся платы при том, что они явно относятся к двум слегка отличающимся семействам. Не распознается лишь одна - как раз та, на которую резисто был напаян (в попытках ее запустить, но реанимировать ее не удалось). Кстати, на этой плаие, если верить схеме, на USB стоит как раз 1.5 кОм.

dimax
dimax аватар
Онлайн
Зарегистрирован: 25.12.2013

andriano пишет:

Может, ее не через USB, а через JTAC/SWD надо подключать?

А вы прям где-то прочли, что с заводской прошивкой можно через USB-CDC можно прошиваться?

andriano
andriano аватар
Offline
Зарегистрирован: 20.06.2015

Где-то прочел: https://wiki.stm32duino.com/index.php?title=Burning_the_bootloader

Most STM32F103 boards do not come with a USB bootloader installed. The main exception is the Maple mini. The STM32F4 series MCU has a built in Serial and USB (DFU) bootloader, so there is no need to install an additional bootloader.

Может я это как-то неправильно понял?

Andy
Andy аватар
Offline
Зарегистрирован: 01.01.2016

Boot select правильно выставил?

andriano
andriano аватар
Offline
Зарегистрирован: 20.06.2015

Вот сейчас спросил у Гугла: "boot select - что это такое". Но, увы, среди предложенных им вариантов ничего толкового не обнаружил.

Чтобы было понятно: с "голым" кристаллом никогда в жизни не работал и не собираюсь, во фьюзах AVR никогда не пытался разбираться и даже не знаю, существует ли что-нибудь аналогичное у stm32. SMD никогда не паял и не собираюсь, т.к. DIP 2.54 паяю в двух разных очках, причем эти очки не совпадают с теми, в которых читаю. А потому раз и навсегда решил для себя, что имею дело только с готовыми платами либо DIP микросхемами и не-SMD рассыпухой.

Да, если boot select - это те два джампера на плате, которыми каким-то образом (не разбирался, каким) определяется порядок загрузки, то они у меня всегда на всех платах стоят в положении по умолчанию - 00. Насколько мне известно, и Arduino и ST-Link работают с платой именно в этом режиме.

 

PS. Сейчас буду пробовать подключить через ST-Link.

Andy
Andy аватар
Offline
Зарегистрирован: 01.01.2016

Загрузчики находятся в System memory

andriano
andriano аватар
Offline
Зарегистрирован: 20.06.2015

Насколько я понял, этот тот режим, в котором нужно прошивать через Serial.

Но я попытался сделать по другому: сначала, естественно, на "кошечках", т.е. прошил блинк в f103 через ST-Link. А когда убедился, что это работает, выяснил, что светодиоды на f407 подключены на PA6 и PA7, подправил блинк под f407 и прошил его. Судя по поведению светодиодов - успешно.

Теперь вопрос - а отлаживаться-то как? Без текстовой консоли - неудобно.

 

PS. Еще непонятка - почему-то Arduino IDE сообщает только о 128К оперативки, хотя, вроде, должно быть 196К.

Andy
Andy аватар
Offline
Зарегистрирован: 01.01.2016

Цитата:
Теперь вопрос - а отлаживаться-то как? Без текстовой консоли - неудобно.
Забей на Ардуино IDE. Ты даже себе представить не можешь какой это отстой. Бери Кейл и получай удовольствие от программирования и отладки. В отладчике ты имеешь доступ ко всем ресурсам микроконтроллера, причем не только по чтению, но и по записи, можешь ставить точки останова, выполнять программу по шагам, видеть код на С и ассемблере. Однажды ты наступишь на Hard Fault и прочувствуешь всю прелесть отладчика. В текстовой консоли этого ничего нет. Если очень хочется можно реализовать консоль, по UART или USB. Берешь готовый пример USB CDC из Кейла, на компе запускаешь виртуальный СОМ порт - вот тебе и консоль.

andriano
andriano аватар
Offline
Зарегистрирован: 20.06.2015

В принципе давно собирался попробовать Keil, но все оказии не подворачивалось. Ну и немного смущает использование инструментального средства с ограничеием 32к для камня, у которого 512к. И где-то здесь читал, что Keil требует обновления прошивки ST-Link.

 

Ладно, возвращаюсь к топику.

Как-то на рабочей неделе не получаетя выкроить время для проекта, поэтому действительно получается "проект выходного дня", но не на одну неделю.

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

Хотел вообще отказаться от отдельных органов управления, но оказалось, что имеющийся у меня экран 320х480 не сенсорный. И, похоже, другие экраны с таким же разрешением - такие же.

Значит - энкодер. Проблема лишь в том, достаточно ли его будет или потребуются дополнительные кнопки.

Ну и подключение самого энкодера: stm32 позволяет подключить энкодер без использования ЦПУ, - используя возможноси аппаратного таймера. Но такое решение ограничивает в выборе пинов. Пока не определился, что лучше: такое аппаратное решение или стандартное решение на прерываниях.

Andy
Andy аватар
Offline
Зарегистрирован: 01.01.2016

andriano пишет:
1. Увы, ни одна из ссылок не помогла.
Поставил драйвер из ссылки в #18, переключил boot select в загрузку из system memory. Все работает, ПО заливается через USB.

andriano пишет:
Может, ее не через USB, а через JTAC/SWD надо подключать?
Можно хоть через UART загружать. Отладка доступна только через JTAG/SWD.

andriano пишет:
Most STM32F103 boards do not come with a USB bootloader installed. The main exception is the Maple mini. The STM32F4 series MCU has a built in Serial and USB (DFU) bootloader, so there is no need to install an additional bootloader.

Может я это как-то неправильно понял?

Скорее всего неправильно. В F103 нет USB загрузчика. В F4 есть и USB и Serial загрузчики.

andriano пишет:
Да, если boot select - это те два джампера на плате, которыми каким-то образом (не разбирался, каким) определяется порядок загрузки, то они у меня всегда на всех платах стоят в положении по умолчанию - 00. Насколько мне известно, и Arduino и ST-Link работают с платой именно в этом режиме.
ST-Link к Boot Select не имеет никакого отношения. ST-Link пишет ПО во Flash вне зависимости от Boot Select. Boot Select лишь говорит ядру откуда начинать исполнение ПО.

andriano пишет:
Насколько я понял, этот тот режим, в котором нужно прошивать через Serial.
Это тот режим в котором можно прошивать через USB, Serial, I2C, SPI. Через ST-Link прошивает в любом случае.

andriano пишет:
Еще непонятка - почему-то Arduino IDE сообщает только о 128К оперативки, хотя, вроде, должно быть 196К.
Это потому, что RAM в F4 разбита на 2 диапазона, в одном могут храниться только данные, в другом данные и инструкции. Ардуино, судя по всему, не умеет работать с двумя диапазонами памяти.

andriano
andriano аватар
Offline
Зарегистрирован: 20.06.2015

1. Переключил джампер на загрузку из системной памяти, в диспетчере устройств появился stm32 BOOTLOADER, и как им теперь воспользоваться?

На попытку прошивки через Arduino IDE отвечает:

Скетч использует 15700 байт (3%) памяти устройства. Всего доступно 514288 байт.
Глобальные переменные используют 7336 байт (5%) динамической памяти, оставляя 123736 байт для локальных переменных. Максимум: 131072 байт.
maple_loader v0.1
Resetting to bootloader via DTR pulse
Reset via USB Serial Failed! Did you select the right serial port?
Searching for DFU device [1EAF:0003]...
Assuming the board is in perpetual bootloader mode and continuing to attempt dfu programming...

dfu-util - (C) 2007-2008 by OpenMoko Inc.
Couldn't find the DFU device: [1EAF:0003]
This program is Free Software and has ABSOLUTELY NO WARRANTY

Found DFU: [0x0483:0xdf11] devnum=255, cfg=0, intf=0, alt=0, name="@Internal Flash  /0x08000000/04*016Kg,01*064Kg,07*128Kg"
Found DFU: [0x0483:0xdf11] devnum=255, cfg=0, intf=0, alt=1, name="@Option Bytes  /0x1FFFC000/01*016 e"
Found DFU: [0x0483:0xdf11] devnum=255, cfg=0, intf=0, alt=2, name="@OTP Memory /0x1FFF7800/01*512 e,01*016 e"
Found DFU: [0x0483:0xdf11] devnum=255, cfg=0, intf=0, alt=3, name="@Device Feature/0xFFFF0000/01*004 e"

timeout waiting for COM5 serial

Честно говоря, по этой диагностике я понял, что с ком-портом - дело труба, а вот с  DFU - то-ли нашел (в строках 13-16), то-ли не может найти (строка 10) - какая-то противоречивая информация.

По факту - светодиоды должным образом не заработали (точнее, вообще не заработали).

Что делать дальше, как им пользоваться?

Andy
Andy аватар
Offline
Зарегистрирован: 01.01.2016

andriano пишет:
и как им теперь воспользоваться?
Program Files(86)/STMicrocontroller/Software/DfuSe находишь там DfuSeDemo это прога, заливающая ПО. Есть и конвертер Hex to Dfu.
andriano пишет:
По факту - светодиоды должным образом не заработали (точнее, вообще не заработали).
Что делать дальше, как им пользоваться?
Светодиоды и не должны работать в этом режиме. МК ждет загрузки ПО через любой доступный порт. В данном случае грузим через USB прогой DfuSeDemo. Далее Boot Select в 00 и reset.

Без ручных манипуляций не обойтись, в этом смысле такой вид загрузки не очень удобен.

andriano
andriano аватар
Offline
Зарегистрирован: 20.06.2015

Тогда непонятна фраза "В F103 нет USB загрузчика. В F4 есть и USB и Serial загрузчики.".

stm32f103 при перестановке перемычек в положение "10" можно прошить через последовательный порт: https://habr.com/post/395577/

stm32f407 - точно так же при перестановке перемычек в положение "10" можно прошить через последовательный порт. 

При этом ни один из них в режиме "00" прошить через встроенный порт нельзя.

Так в чем же разница? Почему считается, что у одного есть загрузчик, а у другого - нет?

 

Далее: мы прошиваем специальный загрузчик, чтобы была возможность пользоваться Arduino IDE через USB порт. В цитате с stm32duino, которую я приводил в сообщении №24, утверждается, что для stm32f407 прошивка загрузчика не нужна, т.к. он уже есть.

В общем, у меня одно с другим никак в сознании не складывается. Так в чем разница между 103 и 407 относительно штатного загрузчика с точки зрения использования Arduino IDE?

 

Andy
Andy аватар
Offline
Зарегистрирован: 01.01.2016

andriano пишет:
Тогда непонятна фраза "В F103 нет USB загрузчика. В F4 есть и USB и Serial загрузчики."
Так в чем же разница? Почему считается, что у одного есть загрузчик, а у другого - нет?
По хорошему, фраза должна звучать так: в F103 есть только один загрузчик через USART1, в F4 - через USART1, USART3, CAN2, USB.
ДШ F103 пишет:
The boot loader is located in System Memory. It is used to reprogram the Flash memory by using USART1.

ДШ F4 пишет:
The boot loader is located in system memory. It is used to reprogram the Flash memory by using USART1 (PA9/PA10), USART3 (PC10/PC11 or PB10/PB11), CAN2 (PB5/PB13), USB OTG FS in Device mode (PA11/PA12) through DFU (device firmware upgrade).

andriano пишет:
В цитате с stm32duino, которую я приводил в сообщении №24, утверждается, что для stm32f407 прошивка загрузчика не нужна, т.к. он уже есть.
Там говорится, что для прошивки загрузчика Ардуино в stm32 можно воспользоваться любым имеющимся в МК загрузчиком. Загрузчик Ардуино нужен в любом случае, встроенный загрузчик не сможет работать с Ардуино IDE.

andriano
andriano аватар
Offline
Зарегистрирован: 20.06.2015

Проект постепенно продвигается: нарисовал и распечатал корпус и конструктивные элементы, соделал экран "полосатым" - через 3 дБ теперь неяркие горизонтальные полоски. Заодно за счет разворачивания циклов немного подросла скорость - тепеь примерно 11 fps. 

При помощи энкодера сделал переключение между 5-ю диапазонами измерения: 5 кГц, 10 кГц, 20 кГц, 40 кГц и 60 кГц. Частотный диапазон ограничен сверху фильтром на микросхеме MAX261, но об этом позже.

Краем уха услышал, что таймеры в stm32 можно использовать для аппаратной поддержки энкодера. В принципе, на прерываниях привычнее, но надо же осваивать новые технологии! Тем более, что свободные таймеры есть: один используется для задания частоты дискретизации звука, другой - для управления фильтром, третий - для энкодера и еще один остается в запасе.

Правда, энкодер работает как-то странно: то на один щелчок изменяет счетчик на 1, то на 2. У меня даже сложилось впечатление, что сначала некоторое время - на 1, а потом переключаеся в режим - на 2. Чтобы не было пропусков, делю значение счетчика на 2 - тогда сначала происходит переключение не на каждый щелчок. Но все лучше, чем если бы перескакивал.

Выяснилось, что высокочастотный сигнал управления фильтром (до 4 МГц) проникает в звуковой тракт и прекрасно виден на экране спектранализатора. Естественно, не сами 4 МГц, а разностные частоты. А разностная частота в данном случае была 4000 - 48*83 = 16 кГц. Вот она и была видна, пока частота дискретизации составляла 48 кГц. Пришлось подкорректировать частоту дискретизации так, чтобы она делила 4 МГц нацело.

Код:

#include "dma.h"

#define LCD_RD              // не исп. // 0x8000 // PB15   - не используется (если использовать, нужно согласовать уровни сигналов)
#define LCD_WR              0x1000 // PB12 // 0x4000 // PB14   
#define LCD_RS              0x0800 // PB11 // 0x2000 // PB13   
#define LCD_CS              0x0002 // PB1 // 0x1000 // PB12          
#define LCD_RESET           0x0001 // PB0 // 0x0800 // PB11   
#define DATA_MASK           0x07f8 // PB3-PB10, данные передаются в разрядах: D0 - PB3, D1 - PB4, ... D7 - PB10
#define CTRL_MASK_DEFAULT   0x1803 // 0xf800 // все управляющие биты HIGH
#define CTRL_MASK_WR_RS_CS  0x0001 // 0x8803 // маска для записи команд
#define CTRL_MASK_WR_CS     0x0801 // 0xa803 // маска для записи данных
#define AUDIO_INPUT         PA0    // аналоговый вход

#define size 1024       // длина буфера оцифровки
float x[size], y[size]; // действительные и мнимые части.
int16_t ADCBuffer[size], ADCBuffer2[size]; // буфера для поочередного ввода данных с АЦП через ПДП (второй буфер обрабатывается ЦПУ)
int16_t * buffADC, * buffFFT; // буфера для ADC/DMA и CPU соответственно
byte stateA = 2; // частота оцифровки ADC: 0 - 11.9кГц, 1 - 23.8кГц, 3 - 47.6кГц, 4 - 95.2кГц, 5 - 142.9кГц
byte stateF = 2; // частота упр.фильтром(фильтра): 0 - 0.5МГц(5.00), 1 - 1МГц(10.00), 2 - 2МГц(20.00), 3 - 4МГц(40.00), 4 - 4МГц(56.27)
const uint16_t dividorADC[5] = {3023, 1511, 755, 377, 251}; // делитель частоты 36 МГц до частоты дискретизации
const uint16_t dividorFilter[5] = {71, 35, 17, 8, 8}; // делитель частоты 36 МГц до управляющей частоты фильтра
const uint8_t filterFreqCode[5] = {26, 26, 26, 26, 0}; // управляющий код фильтра (соответствует отношениям {100,100,100,100,71})
const uint8_t stepScale[5] = {1, 1, 2, 5, 10};
const uint8_t maxScale[5] = {5, 10, 20, 40, 60};
const uint32_t screenScale[5] = {87936, 43968, 21984, 10992, 7328}; // 10922

void Lcd_Write_Com(unsigned char d) { // вывод команды: опустить PB13, вывести байт, поднять PB13
  GPIOB_BASE->ODR = CTRL_MASK_WR_RS_CS | (d << 3);
  GPIOB_BASE->BSRR = LCD_WR;
}

inline void Lcd_Write_Data(unsigned char d) {
  GPIOB_BASE->ODR = CTRL_MASK_WR_CS | (d << 3);
  GPIOB_BASE->BSRR = LCD_WR;
}

inline void Lcd_Write_Pixel(uint16_t d) {
  GPIOB_BASE->ODR = CTRL_MASK_WR_CS | ((d & 0xff00) >> 5);
  GPIOB_BASE->BSRR = LCD_WR;
  GPIOB_BASE->ODR = CTRL_MASK_WR_CS | ((d & 0xff) << 3);
  GPIOB_BASE->BSRR = LCD_WR;
}

inline void Address_set(uint32_t x1, uint32_t y1, uint32_t x2, uint32_t y2) { // позиционирование на экране (оптимизировано)
  GPIOB_BASE->ODR = 0x0151; //0x8953; // команда 0x2a - Column Address Set   // -8803 + 0001
  GPIOB_BASE->BSRR = LCD_WR;

  GPIOB_BASE->ODR = CTRL_MASK_WR_CS | ((x1 & 0xff00)>>5); //
  GPIOB_BASE->BSRR = LCD_WR;

  GPIOB_BASE->ODR = CTRL_MASK_WR_CS | ((x1 & 0xff) << 3); //
  GPIOB_BASE->BSRR = LCD_WR;

  GPIOB_BASE->ODR = CTRL_MASK_WR_CS | ((x2 & 0xff00)>>5); //
  GPIOB_BASE->BSRR = LCD_WR;

  GPIOB_BASE->ODR = CTRL_MASK_WR_CS | ((x2 & 0xff) << 3); //
  GPIOB_BASE->BSRR = LCD_WR;

  GPIOB_BASE->ODR = 0x0159;//0x895b; // команда 0x2b - Page Address Set
  GPIOB_BASE->BSRR = LCD_WR;

  GPIOB_BASE->ODR = CTRL_MASK_WR_CS | ((y1 & 0xff00)>>5); //
  GPIOB_BASE->BSRR = LCD_WR;

  GPIOB_BASE->ODR = CTRL_MASK_WR_CS | ((y1 & 0xff) << 3); //
  GPIOB_BASE->BSRR = LCD_WR;

  GPIOB_BASE->ODR = CTRL_MASK_WR_CS | ((y2 & 0xff00)>>5); //
  GPIOB_BASE->BSRR = LCD_WR;

  GPIOB_BASE->ODR = CTRL_MASK_WR_CS | ((y2 & 0xff) << 3); //
  GPIOB_BASE->BSRR = LCD_WR;

  GPIOB_BASE->ODR = 0x161; //0x8963; // команда 0x2c - Mempry Write
  GPIOB_BASE->BSRR = LCD_WR;
}

void Lcd_Init(void) { // ILI9481 вызываем один раз, поэтому по скорости не оптимизируем
  GPIOB_BASE->BSRR = LCD_RESET;
  delay(5); 
  GPIOB_BASE->BRR = LCD_RESET;
  delay(15);
  GPIOB_BASE->BSRR = LCD_RESET;
  delay(15);

  Lcd_Write_Com(0x11);
  delay(20);
  Lcd_Write_Com(0xD0);
  Lcd_Write_Data(0x07);
  Lcd_Write_Data(0x42);
  Lcd_Write_Data(0x18);
  
  Lcd_Write_Com(0xD1);
  Lcd_Write_Data(0x00);
  Lcd_Write_Data(0x07);
  Lcd_Write_Data(0x10);
  
  Lcd_Write_Com(0xD2);
  Lcd_Write_Data(0x01);
  Lcd_Write_Data(0x02);
  
  Lcd_Write_Com(0xC0);
  Lcd_Write_Data(0x10);
  Lcd_Write_Data(0x3B);
  Lcd_Write_Data(0x00);
  Lcd_Write_Data(0x02);
  Lcd_Write_Data(0x11);
  
  Lcd_Write_Com(0xC5);
  Lcd_Write_Data(0x03);
  Lcd_Write_Com(0x36);
  Lcd_Write_Data(0x0A);
  
  Lcd_Write_Com(0x3A);
  Lcd_Write_Data(0x55); 
  delay(120); 
				
  Lcd_Write_Com(0x29);    //Display ON
  Lcd_Write_Com(0x2c);    // Memory Write
}

void H_line(uint32_t x, uint32_t y, uint32_t l, uint32_t c) {	
  Address_set(x, y, x+l-1, y);
  for(uint32_t i=0; i<l; i++)
  {
      GPIOB_BASE->ODR = CTRL_MASK_WR_CS | ((c >> 5) & DATA_MASK);
      GPIOB_BASE->BSRR = LCD_WR;
      GPIOB_BASE->ODR = CTRL_MASK_WR_CS | ((c << 3) & DATA_MASK);
      GPIOB_BASE->BSRR = LCD_WR;
  }
}

void V_line(uint32_t x, uint32_t y, uint32_t w, uint32_t c) {  
  Address_set(x, y, x, y+w-1);
  for(uint32_t i=0; i<w; i++)
  {
      GPIOB_BASE->ODR = CTRL_MASK_WR_CS | ((c >> 5) & DATA_MASK);
      GPIOB_BASE->BSRR = LCD_WR;
      GPIOB_BASE->ODR = CTRL_MASK_WR_CS | ((c << 3) & DATA_MASK);
      GPIOB_BASE->BSRR = LCD_WR;
  }
}

const float w[size] = {
0.00153398, 0.00460193, 0.00766983, 0.01073766, 0.01380539, 0.01687299, 0.01994043, 0.02300768, 0.02607472, 0.02914151, 0.03220803, 0.03527424, 0.03834012, 0.04140564, 0.04447077, 0.04753548, 
0.05059975, 0.05366354, 0.05672682, 0.05978957, 0.06285176, 0.06591335, 0.06897433, 0.07203465, 0.07509430, 0.07815324, 0.08121145, 0.08426889, 0.08732554, 0.09038136, 0.09343634, 0.09649043, 
0.09954362, 0.10259587, 0.10564715, 0.10869744, 0.11174671, 0.11479493, 0.11784206, 0.12088809, 0.12393298, 0.12697670, 0.13001922, 0.13306053, 0.13610058, 0.13913934, 0.14217680, 0.14521292, 
0.14824768, 0.15128104, 0.15431297, 0.15734346, 0.16037246, 0.16339995, 0.16642590, 0.16945029, 0.17247308, 0.17549425, 0.17851377, 0.18153161, 0.18454774, 0.18756213, 0.19057475, 0.19358559, 
0.19659460, 0.19960176, 0.20260704, 0.20561041, 0.20861185, 0.21161133, 0.21460881, 0.21760427, 0.22059769, 0.22358903, 0.22657826, 0.22956537, 0.23255031, 0.23553306, 0.23851359, 0.24149189, 
0.24446790, 0.24744162, 0.25041301, 0.25338204, 0.25634868, 0.25931292, 0.26227471, 0.26523403, 0.26819086, 0.27114516, 0.27409691, 0.27704608, 0.27999264, 0.28293657, 0.28587783, 0.28881641, 
0.29175226, 0.29468537, 0.29761571, 0.30054324, 0.30346795, 0.30638980, 0.30930876, 0.31222481, 0.31513793, 0.31804808, 0.32095523, 0.32385937, 0.32676045, 0.32965846, 0.33255337, 0.33544515, 
0.33833377, 0.34121920, 0.34410143, 0.34698041, 0.34985613, 0.35272856, 0.35559766, 0.35846342, 0.36132581, 0.36418479, 0.36704035, 0.36989245, 0.37274107, 0.37558618, 0.37842775, 0.38126577, 
0.38410020, 0.38693101, 0.38975817, 0.39258167, 0.39540148, 0.39821756, 0.40102990, 0.40383846, 0.40664322, 0.40944415, 0.41224123, 0.41503442, 0.41782372, 0.42060907, 0.42339047, 0.42616789, 
0.42894129, 0.43171066, 0.43447596, 0.43723717, 0.43999427, 0.44274723, 0.44549602, 0.44824061, 0.45098099, 0.45371712, 0.45644898, 0.45917655, 0.46189979, 0.46461869, 0.46733321, 0.47004333, 
0.47274903, 0.47545028, 0.47814706, 0.48083933, 0.48352708, 0.48621028, 0.48888890, 0.49156292, 0.49423231, 0.49689705, 0.49955711, 0.50221247, 0.50486311, 0.50750899, 0.51015010, 0.51278640, 
0.51541788, 0.51804450, 0.52066625, 0.52328310, 0.52589503, 0.52850200, 0.53110400, 0.53370100, 0.53629298, 0.53887991, 0.54146177, 0.54403853, 0.54661017, 0.54917666, 0.55173799, 0.55429412, 
0.55684504, 0.55939071, 0.56193112, 0.56446624, 0.56699605, 0.56952052, 0.57203963, 0.57455336, 0.57706167, 0.57956456, 0.58206199, 0.58455394, 0.58704039, 0.58952132, 0.59199669, 0.59446650, 
0.59693071, 0.59938930, 0.60184225, 0.60428953, 0.60673113, 0.60916701, 0.61159716, 0.61402156, 0.61644017, 0.61885299, 0.62125998, 0.62366112, 0.62605639, 0.62844577, 0.63082923, 0.63320676, 
0.63557832, 0.63794390, 0.64030348, 0.64265703, 0.64500454, 0.64734597, 0.64968131, 0.65201053, 0.65433362, 0.65665055, 0.65896129, 0.66126584, 0.66356416, 0.66585623, 0.66814204, 0.67042156, 
0.67269477, 0.67496165, 0.67722217, 0.67947632, 0.68172407, 0.68396541, 0.68620031, 0.68842875, 0.69065071, 0.69286617, 0.69507511, 0.69727751, 0.69947334, 0.70166259, 0.70384524, 0.70602126, 
0.70819064, 0.71035335, 0.71250937, 0.71465869, 0.71680128, 0.71893712, 0.72106620, 0.72318849, 0.72530397, 0.72741263, 0.72951444, 0.73160938, 0.73369744, 0.73577859, 0.73785281, 0.73992010, 
0.74198041, 0.74403374, 0.74608007, 0.74811938, 0.75015165, 0.75217685, 0.75419498, 0.75620600, 0.75820991, 0.76020668, 0.76219630, 0.76417874, 0.76615399, 0.76812203, 0.77008284, 0.77203640, 
0.77398269, 0.77592170, 0.77785340, 0.77977779, 0.78169483, 0.78360452, 0.78550683, 0.78740175, 0.78928925, 0.79116933, 0.79304196, 0.79490713, 0.79676481, 0.79861499, 0.80045766, 0.80229280, 
0.80412038, 0.80594039, 0.80775282, 0.80955764, 0.81135485, 0.81314441, 0.81492633, 0.81670057, 0.81846713, 0.82022598, 0.82197712, 0.82372051, 0.82545615, 0.82718403, 0.82890411, 0.83061640, 
0.83232087, 0.83401750, 0.83570628, 0.83738720, 0.83906024, 0.84072537, 0.84238260, 0.84403190, 0.84567325, 0.84730664, 0.84893206, 0.85054948, 0.85215890, 0.85376030, 0.85535366, 0.85693898, 
0.85851622, 0.86008539, 0.86164646, 0.86319942, 0.86474426, 0.86628095, 0.86780950, 0.86932987, 0.87084206, 0.87234606, 0.87384184, 0.87532940, 0.87680872, 0.87827979, 0.87974259, 0.88119711, 
0.88264334, 0.88408126, 0.88551086, 0.88693212, 0.88834503, 0.88974959, 0.89114576, 0.89253356, 0.89391295, 0.89528392, 0.89664647, 0.89800058, 0.89934624, 0.90068343, 0.90201214, 0.90333237, 
0.90464409, 0.90594730, 0.90724198, 0.90852812, 0.90980571, 0.91107473, 0.91233518, 0.91358705, 0.91483031, 0.91606497, 0.91729100, 0.91850839, 0.91971715, 0.92091724, 0.92210867, 0.92329142, 
0.92446547, 0.92563083, 0.92678747, 0.92793539, 0.92907458, 0.93020502, 0.93132671, 0.93243963, 0.93354377, 0.93463913, 0.93572569, 0.93680344, 0.93787238, 0.93893248, 0.93998375, 0.94102618, 
0.94205974, 0.94308444, 0.94410026, 0.94510719, 0.94610523, 0.94709437, 0.94807459, 0.94904588, 0.95000825, 0.95096167, 0.95190614, 0.95284165, 0.95376819, 0.95468575, 0.95559433, 0.95649392, 
0.95738450, 0.95826607, 0.95913862, 0.96000215, 0.96085663, 0.96170208, 0.96253847, 0.96336580, 0.96418406, 0.96499325, 0.96579336, 0.96658437, 0.96736629, 0.96813910, 0.96890280, 0.96965739, 
0.97040284, 0.97113916, 0.97186634, 0.97258437, 0.97329325, 0.97399296, 0.97468351, 0.97536489, 0.97603708, 0.97670009, 0.97735390, 0.97799851, 0.97863392, 0.97926012, 0.97987710, 0.98048486, 
0.98108339, 0.98167269, 0.98225274, 0.98282355, 0.98338511, 0.98393741, 0.98448046, 0.98501423, 0.98553874, 0.98605396, 0.98655991, 0.98705657, 0.98754394, 0.98802202, 0.98849079, 0.98895026, 
0.98940043, 0.98984128, 0.99027281, 0.99069503, 0.99110791, 0.99151147, 0.99190570, 0.99229059, 0.99266614, 0.99303235, 0.99338921, 0.99373672, 0.99407488, 0.99440368, 0.99472312, 0.99503320, 
0.99533391, 0.99562526, 0.99590723, 0.99617983, 0.99644305, 0.99669690, 0.99694136, 0.99717644, 0.99740213, 0.99761844, 0.99782535, 0.99802287, 0.99821100, 0.99838974, 0.99855907, 0.99871901, 
0.99886955, 0.99901069, 0.99914242, 0.99926475, 0.99937767, 0.99948119, 0.99957530, 0.99966000, 0.99973529, 0.99980117, 0.99985764, 0.99990470, 0.99994235, 0.99997059, 0.99998941, 0.99999882, 
0.99999882, 0.99998941, 0.99997059, 0.99994235, 0.99990470, 0.99985764, 0.99980117, 0.99973529, 0.99966000, 0.99957530, 0.99948119, 0.99937767, 0.99926475, 0.99914242, 0.99901069, 0.99886955, 
0.99871901, 0.99855907, 0.99838974, 0.99821100, 0.99802287, 0.99782535, 0.99761844, 0.99740213, 0.99717644, 0.99694136, 0.99669690, 0.99644305, 0.99617983, 0.99590723, 0.99562526, 0.99533391, 
0.99503320, 0.99472312, 0.99440368, 0.99407488, 0.99373672, 0.99338921, 0.99303235, 0.99266614, 0.99229059, 0.99190570, 0.99151147, 0.99110791, 0.99069503, 0.99027281, 0.98984128, 0.98940043, 
0.98895026, 0.98849079, 0.98802202, 0.98754394, 0.98705657, 0.98655991, 0.98605396, 0.98553874, 0.98501423, 0.98448046, 0.98393741, 0.98338511, 0.98282355, 0.98225274, 0.98167269, 0.98108339, 
0.98048486, 0.97987710, 0.97926012, 0.97863392, 0.97799851, 0.97735390, 0.97670009, 0.97603708, 0.97536489, 0.97468351, 0.97399296, 0.97329325, 0.97258437, 0.97186634, 0.97113916, 0.97040284, 
0.96965739, 0.96890280, 0.96813910, 0.96736629, 0.96658437, 0.96579336, 0.96499325, 0.96418406, 0.96336580, 0.96253847, 0.96170208, 0.96085663, 0.96000215, 0.95913862, 0.95826607, 0.95738450, 
0.95649392, 0.95559433, 0.95468575, 0.95376819, 0.95284165, 0.95190614, 0.95096167, 0.95000825, 0.94904588, 0.94807459, 0.94709437, 0.94610523, 0.94510719, 0.94410026, 0.94308444, 0.94205974, 
0.94102618, 0.93998375, 0.93893248, 0.93787238, 0.93680344, 0.93572569, 0.93463913, 0.93354377, 0.93243963, 0.93132671, 0.93020502, 0.92907458, 0.92793539, 0.92678747, 0.92563083, 0.92446547, 
0.92329142, 0.92210867, 0.92091724, 0.91971715, 0.91850839, 0.91729100, 0.91606497, 0.91483031, 0.91358705, 0.91233518, 0.91107473, 0.90980571, 0.90852812, 0.90724198, 0.90594730, 0.90464409, 
0.90333237, 0.90201214, 0.90068343, 0.89934624, 0.89800058, 0.89664647, 0.89528392, 0.89391295, 0.89253356, 0.89114576, 0.88974959, 0.88834503, 0.88693212, 0.88551086, 0.88408126, 0.88264334, 
0.88119711, 0.87974259, 0.87827979, 0.87680872, 0.87532940, 0.87384184, 0.87234606, 0.87084206, 0.86932987, 0.86780950, 0.86628095, 0.86474426, 0.86319942, 0.86164646, 0.86008539, 0.85851622, 
0.85693898, 0.85535366, 0.85376030, 0.85215890, 0.85054948, 0.84893206, 0.84730664, 0.84567325, 0.84403190, 0.84238260, 0.84072537, 0.83906024, 0.83738720, 0.83570628, 0.83401750, 0.83232087, 
0.83061640, 0.82890411, 0.82718403, 0.82545615, 0.82372051, 0.82197712, 0.82022598, 0.81846713, 0.81670057, 0.81492633, 0.81314441, 0.81135485, 0.80955764, 0.80775282, 0.80594039, 0.80412038, 
0.80229280, 0.80045766, 0.79861499, 0.79676481, 0.79490713, 0.79304196, 0.79116933, 0.78928925, 0.78740175, 0.78550683, 0.78360452, 0.78169483, 0.77977779, 0.77785340, 0.77592170, 0.77398269, 
0.77203640, 0.77008284, 0.76812203, 0.76615399, 0.76417874, 0.76219630, 0.76020668, 0.75820991, 0.75620600, 0.75419498, 0.75217685, 0.75015165, 0.74811938, 0.74608007, 0.74403374, 0.74198041, 
0.73992010, 0.73785281, 0.73577859, 0.73369744, 0.73160938, 0.72951444, 0.72741263, 0.72530397, 0.72318849, 0.72106620, 0.71893712, 0.71680128, 0.71465869, 0.71250937, 0.71035335, 0.70819064, 
0.70602126, 0.70384524, 0.70166259, 0.69947334, 0.69727751, 0.69507511, 0.69286617, 0.69065071, 0.68842875, 0.68620031, 0.68396541, 0.68172407, 0.67947632, 0.67722217, 0.67496165, 0.67269477, 
0.67042156, 0.66814204, 0.66585623, 0.66356416, 0.66126584, 0.65896129, 0.65665055, 0.65433362, 0.65201053, 0.64968131, 0.64734597, 0.64500454, 0.64265703, 0.64030348, 0.63794390, 0.63557832, 
0.63320676, 0.63082923, 0.62844577, 0.62605639, 0.62366112, 0.62125998, 0.61885299, 0.61644017, 0.61402156, 0.61159716, 0.60916701, 0.60673113, 0.60428953, 0.60184225, 0.59938930, 0.59693071, 
0.59446650, 0.59199669, 0.58952132, 0.58704039, 0.58455394, 0.58206199, 0.57956456, 0.57706167, 0.57455336, 0.57203963, 0.56952052, 0.56699605, 0.56446624, 0.56193112, 0.55939071, 0.55684504, 
0.55429412, 0.55173799, 0.54917666, 0.54661017, 0.54403853, 0.54146177, 0.53887991, 0.53629298, 0.53370100, 0.53110400, 0.52850200, 0.52589503, 0.52328310, 0.52066625, 0.51804450, 0.51541788, 
0.51278640, 0.51015010, 0.50750899, 0.50486311, 0.50221247, 0.49955711, 0.49689705, 0.49423231, 0.49156292, 0.48888890, 0.48621028, 0.48352708, 0.48083933, 0.47814706, 0.47545028, 0.47274903, 
0.47004333, 0.46733321, 0.46461869, 0.46189979, 0.45917655, 0.45644898, 0.45371712, 0.45098099, 0.44824061, 0.44549602, 0.44274723, 0.43999427, 0.43723717, 0.43447596, 0.43171066, 0.42894129, 
0.42616789, 0.42339047, 0.42060907, 0.41782372, 0.41503442, 0.41224123, 0.40944415, 0.40664322, 0.40383846, 0.40102990, 0.39821756, 0.39540148, 0.39258167, 0.38975817, 0.38693101, 0.38410020, 
0.38126577, 0.37842775, 0.37558618, 0.37274107, 0.36989245, 0.36704035, 0.36418479, 0.36132581, 0.35846342, 0.35559766, 0.35272856, 0.34985613, 0.34698041, 0.34410143, 0.34121920, 0.33833377, 
0.33544515, 0.33255337, 0.32965846, 0.32676045, 0.32385937, 0.32095523, 0.31804808, 0.31513793, 0.31222481, 0.30930876, 0.30638980, 0.30346795, 0.30054324, 0.29761571, 0.29468537, 0.29175226, 
0.28881641, 0.28587783, 0.28293657, 0.27999264, 0.27704608, 0.27409691, 0.27114516, 0.26819086, 0.26523403, 0.26227471, 0.25931292, 0.25634868, 0.25338204, 0.25041301, 0.24744162, 0.24446790, 
0.24149189, 0.23851359, 0.23553306, 0.23255031, 0.22956537, 0.22657826, 0.22358903, 0.22059769, 0.21760427, 0.21460881, 0.21161133, 0.20861185, 0.20561041, 0.20260704, 0.19960176, 0.19659460, 
0.19358559, 0.19057475, 0.18756213, 0.18454774, 0.18153161, 0.17851377, 0.17549425, 0.17247308, 0.16945029, 0.16642590, 0.16339995, 0.16037246, 0.15734346, 0.15431297, 0.15128104, 0.14824768, 
0.14521292, 0.14217680, 0.13913934, 0.13610058, 0.13306053, 0.13001922, 0.12697670, 0.12393298, 0.12088809, 0.11784206, 0.11479493, 0.11174671, 0.10869744, 0.10564715, 0.10259587, 0.09954362, 
0.09649043, 0.09343634, 0.09038136, 0.08732554, 0.08426889, 0.08121145, 0.07815324, 0.07509430, 0.07203465, 0.06897433, 0.06591335, 0.06285176, 0.05978957, 0.05672682, 0.05366354, 0.05059975, 
0.04753548, 0.04447077, 0.04140564, 0.03834012, 0.03527424, 0.03220803, 0.02914151, 0.02607472, 0.02300768, 0.01994043, 0.01687299, 0.01380539, 0.01073766, 0.00766983, 0.00460193, 0.00153398};

inline void FFT() { // 1024 отсчета
    int32_t i,i1,j,k,l,l1,l2;
    float c1,c2,tx,ty,t1,t2,u1,u2,z;

    /* Do the bit reversal */
    j = 0;
    for (i = 0; i < 1023; i++) {
        if (i < j) {
            tx = x[i];
            ty = y[i];
            x[i] = x[j];
            y[i] = y[j];
            x[j] = tx;
            y[j] = ty;
        }
        k = 512;
        while (k <= j) {
            j -= k;
            k >>= 1;
        }
        j += k;
    }

    /* Compute the FFT */
    c1 = -1.0;
    c2 = 0.0;
    l2 = 1;
    for (l = 0; l < 10; l++) {
        l1 = l2;
        l2 <<= 1;
        u1 = 1.0;
        u2 = 0.0;
        for (j = 0; j < l1; j++) {
            for (i = j; i < 1024; i += l2) {
                i1 = i + l1;
                t1 = u1 * x[i1] - u2 * y[i1];
                t2 = u1 * y[i1] + u2 * x[i1];
                x[i1] = x[i] - t1;
                y[i1] = y[i] - t2;
                x[i] += t1;
                y[i] += t2;
            }
            z =  u1 * c1 - u2 * c2;
            u2 = u1 * c2 + u2 * c1;
            u1 = z;
        }
        c2 = -sqrt((1.0 - c1) / 2.0);
        c1 = sqrt((1.0 + c1) / 2.0);
    }

    /* Calculate the absolute value */
//        for (i = 0; i < 1024; i++) {
//            y[i] = (y[i]*y[i] + x[i]*x[i]); // /1024;
//        }
}

void DMA_init(void) {
    RCC_BASE->AHBENR |= 1; //DMA1EN - вкл.тактирования DMA1, все остальное (таймер, порты, ADC, RCC_PCLK2_Div6) stm32duino по умолчанию уже включило
    
// DMA1->CCR1: MEM2MEM=0, PL=2, MSIZE=01, PSIZE=01, MINC=1, PINC=0, CIRC=0, DIR=0, TEIE=0, HTIE=0, TCIE=0, EN=1 
    DMA1_BASE->CCR1 |= 0x2000; // PL=2 (bits 13-12) - high
    DMA1_BASE->CCR1 |= 0x0400; // MSIZE=01 (bits 11-10) - 16 bits
    DMA1_BASE->CCR1 |= 0x0100; // PIZE=01 (bits 9-8) - 16 bits
    DMA1_BASE->CCR1 |= 0x0080; // MINC (bit 7) инкрементировать адреса в памяти
    DMA1_BASE->CNDTR1 = size;  // сколько сэмплов передать (read only when channel disabled)
    DMA1_BASE->CPAR1 = (uint32_t)&ADC1_BASE->DR; // читать из ADC1
    DMA1_BASE->CMAR1 = (uint32_t)buffADC; // куда писать
    
    DMA1_BASE->CCR1 |= 1; // EN (bit 0) 0 включаем
}

void ADC_init() {
    int a = analogRead(AUDIO_INPUT);
 
// ADC1->CR1: AWDEN=0, JAWDEN=0, DUALMOD=0(Independed), DISCNUM=0, JDISCEN=0, DISCEN=0, JAUTO=0, AWDSGL=0, SCAN=0, JEOCIE=0, AWDIE=0, EOCIE=0, AWDCH=0
// ADC1->CR2: TSVREFE=0, SWSTART=0, JSWSTART=0, EXTTRIG=1, EXTCEL=3(Tim2-CC2), JEXTTRIG=0, JEXTCEL=0, ALIGN=0, DMA=1, RSTCAL=0, CAL=0, CONT=0, ADON=1
// ADC1->SQR1.l=0 (one channel), SQR3.SQ1=0 (first channel)
    ADC1_BASE->CR2 |= 0x00100000; // EXTTRIG (bit 20)
////////  ADC1_BASE->CR2 |= 0x00000002; // CONT=1 (bit 1) - это непрерывное преобразование без таймера ???
    ADC1_BASE->CR2 &= 0xfff1ffff;
//    ADC1_BASE->CR2 |= 0x00060000; // EXTCEL=3 (bit 19-17) Tim2CC2
    ADC1_BASE->CR2 |= 0x000a0000; // EXTCEL=5 (bit 19-17) Tim4CC4
    ADC1_BASE->CR2 |= 0x00000100; // DMA (bit 8)
    ADC1_BASE->CR2 |= 1; // ADON
}

void TIMER2_init() { // channel 2 - 4 MHz for filter
// default by stm32duino: TIMER2->CR1.ARPE=1, CR1.CEN=1, CCMR1/2=[0x68], DMAR=0x81, EGR.CC2G=1, EGR.UG=1 (?), CCER.CC2E=1
    TIMER2_BASE->ARR = dividorFilter[stateF]; // 8; // 36000k/9=4000k - верхняя частота для фильтра
    TIMER2_BASE->CCR2 = (dividorFilter[stateF] + 1)/2; // 4; // 8/2=4
    TIMER2_BASE->CCER = 0x0010;  // CC2 enable
}

void TIMER4_init() { // channel 4 - 48 kHz to ADC
// default by stm32duino: TIMER4->CR1.ARPE=1, CR1.CEN=1, CCMR1/2=[0x68], DMAR=0x81, EGR.CC2G=1, EGR.UG=1 (?), CCER.CC2E=1
    TIMER4_BASE->ARR = dividorADC[stateA]; // 251; //// 749; // 36000k/750=48k == делитель=756, частота 47.619 - чтобы не было разностных частот (16к)
    TIMER4_BASE->CCR4 = (dividorADC[stateA] + 1)/2; // 126; // 374; // 750/2=375
    TIMER4_BASE->CCER = 0x1000;  // CC4 enable
}

void TIMER1_init() { // настраиваем для использования энкодера на ногах PA8 и PA9
  TIMER1_BASE->CR1 = 0; // Временно выключаем таймер
  TIMER1_BASE->SMCR  = TIMER_SMCR_SMS_ENCODER2;
  TIMER1_BASE->CCMR1 = TIMER_CCMR1_IC1F | TIMER_CCMR1_IC2F;
  TIMER1_BASE->CCMR1 |= TIMER_CCMR1_CC1S_INPUT_TI1 | TIMER_CCMR1_CC2S_INPUT_TI2;
  TIMER1_BASE->CCMR2 = 0; ///???///
  TIMER1_BASE->CCER = 0;
  TIMER1_BASE->CNT = 4;
  TIMER1_BASE->PSC = 0;
  TIMER1_BASE->ARR = 9;
  TIMER1_BASE->CCR1 = 9;
  TIMER1_BASE->CCR2 = 9;
  TIMER1_BASE->CCR3 = 0;
  TIMER1_BASE->CCR4 = 0;
  TIMER1_BASE->DMAR = 0;

  TIMER1_BASE->CR1 = 1; // Включаем
}

void inline resetDMA(){
  DMA1_BASE->CCR1 &= 0xfffffffe; // EN (bit 0)
  DMA1_BASE->CNDTR1 = size;
  DMA1_BASE->IFCR = 0x0000000f;
  int16_t * tmp = buffFFT;
  buffFFT = buffADC;
  buffADC = tmp;
  DMA1_BASE->CMAR1 = (uint32_t)buffADC; // куда писать
  DMA1_BASE->CCR1 |= 1;
}

union ct {
  float f;
  uint32_t d;
}; 

int getLog6(float b) {
  ct c;
  c.f = b;
  if     ((c.d & 0x007fffff) >= 6558192) return ((c.d >> 23) & 0xff)*6 + 5;
  else if((c.d & 0x007fffff) >= 4927477) return ((c.d >> 23) & 0xff)*6 + 4;
  else if((c.d & 0x007fffff) >= 3474675) return ((c.d >> 23) & 0xff)*6 + 3;
  else if((c.d & 0x007fffff) >= 2180375) return ((c.d >> 23) & 0xff)*6 + 2;
  else if((c.d & 0x007fffff) >= 1027286) return ((c.d >> 23) & 0xff)*6 + 1;
  else                                   return ((c.d >> 23) & 0xff)*6;

}

#define fontdatatype uint8_t
extern fontdatatype SmallFont[];

/*void writeLetter(uint16_t x, uint16_t y, uint16_t color, uint8_t ch) {
  Address_set(x, y, x+7, y+4);
  int offs = (ch-32)*5 + 4;
  for(int j = 0; j < 5; j++) {
    for(int i = 0; i < 8; i++) {
      if(SmallFont[offs+j] & (1 << i))
        Lcd_Write_Pixel(color);
      else
        Lcd_Write_Pixel(0);
    }
  }
}*/

void writeLetter_x2(uint32_t x, uint32_t y, uint16_t color, uint8_t ch) {
  Address_set(x, y, x+15, y+9);
  int offs = (ch-32)*5 + 4;
  for(int j = 0; j < 5; j++) {
   for(int k = 0; k < 2; k++)
    for(int i = 0; i < 8; i++) {
      if(SmallFont[offs+j] & (1 << i)) {
        Lcd_Write_Pixel(color);
        Lcd_Write_Pixel(color);
      } else {
        Lcd_Write_Pixel(0);
        Lcd_Write_Pixel(0);
      }
    }
  }
}

void writeNumber_2(int y, int n) { // написать 1-2-значное число n с центром по координате y
  if(n < 10) {
    writeLetter_x2(303, y - 4, 0xffff, 48 + n);
  } else {
    writeLetter_x2(303, y - 12, 0xffff, 48 + n/10);
    writeLetter_x2(303, y +  0, 0xffff, 48 + n%10);
  }
}

void putScale() {
  Address_set(300, 0, 319, 479);
  for (int i = 0; i < 20*480; i++) {
    Lcd_Write_Pixel(0);
  }
  V_line(300, 0, 480, 0xffff);
  for(int i = 0; i <= maxScale[stateA]; i++) {
    H_line(300, i*screenScale[stateA]/1024 /*512.0/m*/, 4, 0xffff);
    if((i > 0) && ((i % stepScale[stateA]) == 0)) {
      writeNumber_2(i*screenScale[stateA]/1024 /*512.0/m*/, i);
    }
  }
}

#define BLUE_LO 0x00f8
#define LIGHT_BLUE_LO 0x04f8
#define RED_HI 0x07c0
#define LIGHT_RED_LO 0x0400
int screenX, screenY;

void H_line_b(int x, int y, int s, uint16_t color) { // рисование синей линии с полосками каждый 12-й пиксель (считая через 3 дБ по 2 пикселя на 0.5 дБ)
  int s0 = s/6; // (0, i-1, s*2, 0x001f)
  int s1 = s%6;

  Address_set(x, y, x+s*2-1, y);
  for(uint32_t i = 0; i < s0; i++)
  {
      GPIOB_BASE->ODR = CTRL_MASK_WR_CS; // | ((c >> 5) & DATA_MASK); // (001f >> 5) = 0
      GPIOB_BASE->BSRR = LCD_WR;
      GPIOB_BASE->ODR = CTRL_MASK_WR_CS | BLUE_LO; //((c << 3) & DATA_MASK); // (001f << 3) = 00f8
      GPIOB_BASE->BSRR = LCD_WR;
      GPIOB_BASE->ODR = CTRL_MASK_WR_CS; // | ((c >> 5) & DATA_MASK); // (001f >> 5) = 0
      GPIOB_BASE->BSRR = LCD_WR;
      GPIOB_BASE->ODR = CTRL_MASK_WR_CS | BLUE_LO; //((c << 3) & DATA_MASK); // (001f << 3) = 00f8
      GPIOB_BASE->BSRR = LCD_WR;
      
      GPIOB_BASE->ODR = CTRL_MASK_WR_CS; // | ((c >> 5) & DATA_MASK); // (001f >> 5) = 0
      GPIOB_BASE->BSRR = LCD_WR;
      GPIOB_BASE->ODR = CTRL_MASK_WR_CS | BLUE_LO; //((c << 3) & DATA_MASK); // (001f << 3) = 00f8
      GPIOB_BASE->BSRR = LCD_WR;
      GPIOB_BASE->ODR = CTRL_MASK_WR_CS; // | ((c >> 5) & DATA_MASK); // (001f >> 5) = 0
      GPIOB_BASE->BSRR = LCD_WR;
      GPIOB_BASE->ODR = CTRL_MASK_WR_CS | BLUE_LO; //((c << 3) & DATA_MASK); // (001f << 3) = 00f8
      GPIOB_BASE->BSRR = LCD_WR;
      
      GPIOB_BASE->ODR = CTRL_MASK_WR_CS; // | ((c >> 5) & DATA_MASK); // (001f >> 5) = 0
      GPIOB_BASE->BSRR = LCD_WR;
      GPIOB_BASE->ODR = CTRL_MASK_WR_CS | BLUE_LO; //((c << 3) & DATA_MASK); // (001f << 3) = 00f8
      GPIOB_BASE->BSRR = LCD_WR;
      GPIOB_BASE->ODR = CTRL_MASK_WR_CS; // | ((c >> 5) & DATA_MASK); // (001f >> 5) = 0
      GPIOB_BASE->BSRR = LCD_WR;
      GPIOB_BASE->ODR = CTRL_MASK_WR_CS | BLUE_LO; //((c << 3) & DATA_MASK); // (001f << 3) = 00f8
      GPIOB_BASE->BSRR = LCD_WR;
      
      GPIOB_BASE->ODR = CTRL_MASK_WR_CS; // | ((c >> 5) & DATA_MASK); // (001f >> 5) = 0
      GPIOB_BASE->BSRR = LCD_WR;
      GPIOB_BASE->ODR = CTRL_MASK_WR_CS | BLUE_LO; //((c << 3) & DATA_MASK); // (001f << 3) = 00f8
      GPIOB_BASE->BSRR = LCD_WR;
      GPIOB_BASE->ODR = CTRL_MASK_WR_CS; // | ((c >> 5) & DATA_MASK); // (001f >> 5) = 0
      GPIOB_BASE->BSRR = LCD_WR;
      GPIOB_BASE->ODR = CTRL_MASK_WR_CS | BLUE_LO; //((c << 3) & DATA_MASK); // (001f << 3) = 00f8
      GPIOB_BASE->BSRR = LCD_WR;
      
      GPIOB_BASE->ODR = CTRL_MASK_WR_CS; // | ((c >> 5) & DATA_MASK); // (001f >> 5) = 0
      GPIOB_BASE->BSRR = LCD_WR;
      GPIOB_BASE->ODR = CTRL_MASK_WR_CS | BLUE_LO; //((c << 3) & DATA_MASK); // (001f << 3) = 00f8
      GPIOB_BASE->BSRR = LCD_WR;
      GPIOB_BASE->ODR = CTRL_MASK_WR_CS; // | ((c >> 5) & DATA_MASK); // (001f >> 5) = 0
      GPIOB_BASE->BSRR = LCD_WR;
      GPIOB_BASE->ODR = CTRL_MASK_WR_CS | BLUE_LO; //((c << 3) & DATA_MASK); // (001f << 3) = 00f8
      GPIOB_BASE->BSRR = LCD_WR;
      
      GPIOB_BASE->ODR = CTRL_MASK_WR_CS; // | ((c >> 5) & DATA_MASK); // (001f >> 5) = 0
      GPIOB_BASE->BSRR = LCD_WR;
      GPIOB_BASE->ODR = CTRL_MASK_WR_CS | BLUE_LO; //((c << 3) & DATA_MASK); // (001f << 3) = 00f8
      GPIOB_BASE->BSRR = LCD_WR;
      GPIOB_BASE->ODR = CTRL_MASK_WR_CS; // | ((c >> 5) & DATA_MASK); // (001f >> 5) = 0
      GPIOB_BASE->BSRR = LCD_WR;
      GPIOB_BASE->ODR = CTRL_MASK_WR_CS | LIGHT_BLUE_LO; //((c << 3) & DATA_MASK); // (001f << 3) = 00f8
      GPIOB_BASE->BSRR = LCD_WR;
  }
  if (s1 > 0)
    for(uint32_t i = 0; i < s1; i++)
    {
      GPIOB_BASE->ODR = CTRL_MASK_WR_CS; // | ((c >> 5) & DATA_MASK);
      GPIOB_BASE->BSRR = LCD_WR;
      GPIOB_BASE->ODR = CTRL_MASK_WR_CS | BLUE_LO; //((c << 3) & DATA_MASK);
      GPIOB_BASE->BSRR = LCD_WR;
      GPIOB_BASE->ODR = CTRL_MASK_WR_CS; // | ((c >> 5) & DATA_MASK);
      GPIOB_BASE->BSRR = LCD_WR;
      GPIOB_BASE->ODR = CTRL_MASK_WR_CS | BLUE_LO; //((c << 3) & DATA_MASK);
      GPIOB_BASE->BSRR = LCD_WR;
    }
}

void H_line_r(int x, int y, int s, uint16_t color) { // рисование синей линии с полосками каждый 12-й пиксель (считая через 3 дБ по 2 пикселя на 0.5 дБ)
  int s0 = s/6; // (0, i-1, s*2, 0xf800)
  int s1 = s%6;

  Address_set(x*2, y, x*2+s*2-1, y);
  if (s1 > 1)
    for(uint32_t i = 1; i < s1; i++)
    {
      GPIOB_BASE->ODR = CTRL_MASK_WR_CS | RED_HI; // | ((c >> 5) & DATA_MASK); // (f800 >> 5) = 07c0
      GPIOB_BASE->BSRR = LCD_WR;
      GPIOB_BASE->ODR = CTRL_MASK_WR_CS; // | 0xf8; //((c << 3) & DATA_MASK); // (f800 << 3) = 7c000, X & MASK = 0
      GPIOB_BASE->BSRR = LCD_WR;
      GPIOB_BASE->ODR = CTRL_MASK_WR_CS | RED_HI; // | ((c >> 5) & DATA_MASK); // (f800 >> 5) = 07c0
      GPIOB_BASE->BSRR = LCD_WR;
      GPIOB_BASE->ODR = CTRL_MASK_WR_CS; // | 0xf8; //((c << 3) & DATA_MASK); // (f800 << 3) = 7c000, X & MASK = 0
      GPIOB_BASE->BSRR = LCD_WR;
    }
  if (s1 > 0)
    {
      GPIOB_BASE->ODR = CTRL_MASK_WR_CS | RED_HI; // | ((c >> 5) & DATA_MASK); // (f800 >> 5) = 07c0
      GPIOB_BASE->BSRR = LCD_WR;
      GPIOB_BASE->ODR = CTRL_MASK_WR_CS; // | 0xf8; //((c << 3) & DATA_MASK); // (f800 << 3) = 7c000, X & MASK = 0
      GPIOB_BASE->BSRR = LCD_WR;
      GPIOB_BASE->ODR = CTRL_MASK_WR_CS | RED_HI; // | ((c >> 5) & DATA_MASK); // (f800 >> 5) = 07c0
      GPIOB_BASE->BSRR = LCD_WR;
      GPIOB_BASE->ODR = CTRL_MASK_WR_CS | LIGHT_RED_LO; // | 0xf8; //((c << 3) & DATA_MASK); // (f800 << 3) = 7c000, X & MASK = 0
      GPIOB_BASE->BSRR = LCD_WR;
    }
  for(uint32_t i = 0; i < s0; i++)
  {
      GPIOB_BASE->ODR = CTRL_MASK_WR_CS | RED_HI; // | ((c >> 5) & DATA_MASK); // (f800 >> 5) = 07c0
      GPIOB_BASE->BSRR = LCD_WR;
      GPIOB_BASE->ODR = CTRL_MASK_WR_CS; // | 0xf8; //((c << 3) & DATA_MASK); // (f800 << 3) = 7c000, X & MASK = 0
      GPIOB_BASE->BSRR = LCD_WR;
      GPIOB_BASE->ODR = CTRL_MASK_WR_CS | RED_HI; // | ((c >> 5) & DATA_MASK); // (f800 >> 5) = 07c0
      GPIOB_BASE->BSRR = LCD_WR;
      GPIOB_BASE->ODR = CTRL_MASK_WR_CS; // | 0xf8; //((c << 3) & DATA_MASK); // (f800 << 3) = 7c000, X & MASK = 0
      GPIOB_BASE->BSRR = LCD_WR;
      
      GPIOB_BASE->ODR = CTRL_MASK_WR_CS | RED_HI; // | ((c >> 5) & DATA_MASK); // (f800 >> 5) = 07c0
      GPIOB_BASE->BSRR = LCD_WR;
      GPIOB_BASE->ODR = CTRL_MASK_WR_CS; // | 0xf8; //((c << 3) & DATA_MASK); // (f800 << 3) = 7c000, X & MASK = 0
      GPIOB_BASE->BSRR = LCD_WR;
      GPIOB_BASE->ODR = CTRL_MASK_WR_CS | RED_HI; // | ((c >> 5) & DATA_MASK); // (f800 >> 5) = 07c0
      GPIOB_BASE->BSRR = LCD_WR;
      GPIOB_BASE->ODR = CTRL_MASK_WR_CS; // | 0xf8; //((c << 3) & DATA_MASK); // (f800 << 3) = 7c000, X & MASK = 0
      GPIOB_BASE->BSRR = LCD_WR;
      
      GPIOB_BASE->ODR = CTRL_MASK_WR_CS | RED_HI; // | ((c >> 5) & DATA_MASK); // (f800 >> 5) = 07c0
      GPIOB_BASE->BSRR = LCD_WR;
      GPIOB_BASE->ODR = CTRL_MASK_WR_CS; // | 0xf8; //((c << 3) & DATA_MASK); // (f800 << 3) = 7c000, X & MASK = 0
      GPIOB_BASE->BSRR = LCD_WR;
      GPIOB_BASE->ODR = CTRL_MASK_WR_CS | RED_HI; // | ((c >> 5) & DATA_MASK); // (f800 >> 5) = 07c0
      GPIOB_BASE->BSRR = LCD_WR;
      GPIOB_BASE->ODR = CTRL_MASK_WR_CS; // | 0xf8; //((c << 3) & DATA_MASK); // (f800 << 3) = 7c000, X & MASK = 0
      GPIOB_BASE->BSRR = LCD_WR;
      
      GPIOB_BASE->ODR = CTRL_MASK_WR_CS | RED_HI; // | ((c >> 5) & DATA_MASK); // (f800 >> 5) = 07c0
      GPIOB_BASE->BSRR = LCD_WR;
      GPIOB_BASE->ODR = CTRL_MASK_WR_CS; // | 0xf8; //((c << 3) & DATA_MASK); // (f800 << 3) = 7c000, X & MASK = 0
      GPIOB_BASE->BSRR = LCD_WR;
      GPIOB_BASE->ODR = CTRL_MASK_WR_CS | RED_HI; // | ((c >> 5) & DATA_MASK); // (f800 >> 5) = 07c0
      GPIOB_BASE->BSRR = LCD_WR;
      GPIOB_BASE->ODR = CTRL_MASK_WR_CS; // | 0xf8; //((c << 3) & DATA_MASK); // (f800 << 3) = 7c000, X & MASK = 0
      GPIOB_BASE->BSRR = LCD_WR;
      
      GPIOB_BASE->ODR = CTRL_MASK_WR_CS | RED_HI; // | ((c >> 5) & DATA_MASK); // (f800 >> 5) = 07c0
      GPIOB_BASE->BSRR = LCD_WR;
      GPIOB_BASE->ODR = CTRL_MASK_WR_CS; // | 0xf8; //((c << 3) & DATA_MASK); // (f800 << 3) = 7c000, X & MASK = 0
      GPIOB_BASE->BSRR = LCD_WR;
      GPIOB_BASE->ODR = CTRL_MASK_WR_CS | RED_HI; // | ((c >> 5) & DATA_MASK); // (f800 >> 5) = 07c0
      GPIOB_BASE->BSRR = LCD_WR;
      GPIOB_BASE->ODR = CTRL_MASK_WR_CS; // | 0xf8; //((c << 3) & DATA_MASK); // (f800 << 3) = 7c000, X & MASK = 0
      GPIOB_BASE->BSRR = LCD_WR;
      
      GPIOB_BASE->ODR = CTRL_MASK_WR_CS | RED_HI; // | ((c >> 5) & DATA_MASK); // (f800 >> 5) = 07c0
      GPIOB_BASE->BSRR = LCD_WR;
      GPIOB_BASE->ODR = CTRL_MASK_WR_CS; // | 0xf8; //((c << 3) & DATA_MASK); // (f800 << 3) = 7c000, X & MASK = 0
      GPIOB_BASE->BSRR = LCD_WR;
      GPIOB_BASE->ODR = CTRL_MASK_WR_CS | RED_HI; // | ((c >> 5) & DATA_MASK); // (f800 >> 5) = 07c0
      GPIOB_BASE->BSRR = LCD_WR;
      GPIOB_BASE->ODR = CTRL_MASK_WR_CS | LIGHT_RED_LO; // | 0xf8; //((c << 3) & DATA_MASK); // (f800 << 3) = 7c000, X & MASK = 0
      GPIOB_BASE->BSRR = LCD_WR;
  }
}

void touchScreen() {    // PB0, PB1 - всегда INPUT, X- и X+ поменять местами
/*   pinMode(PB13, INPUT);    // Y+ A2 : RS  - PB13 connect to PB0
   pinMode( PB3, INPUT);    // Y- 8  : D0  - PB3
   pinMode(PB4, OUTPUT);    // X+           PB4
   digitalWrite(PB4, HIGH); // X+ 9  : D1 - PB4
   pinMode(PB12, OUTPUT);    // X- A3 : CS - PB12 connect to PB1
   digitalWrite(PB12, LOW);  // X-           PB12 connect to PB1
   screenX = analogRead(PB0);

   pinMode( PB4, INPUT);     // X+           PB4
   pinMode(PB12, INPUT);     // X-           PB12 connect to PB1
   pinMode(PB13, OUTPUT);    // Y+           PB13
   digitalWrite(PB13, HIGH); // Y+           PB13
   pinMode(PB3, OUTPUT);    // Y-           PB3
   digitalWrite(PB3, LOW);  // Y-           PB3
   screenY = analogRead(PB1);
   
   pinMode( PB4, OUTPUT);     // X+           PB4
   pinMode(PB12, OUTPUT);     // X-           PB12 connect to PB1
   digitalWrite(PB12, HIGH);  // X-           PB12 connect to PB1
   */
}

void checkEncoder() {
  static int32_t oldEncoder = 15;
  int32_t encoder = (TIMER1_BASE->CNT)/2;
  if(encoder != oldEncoder) {
    Serial.print("Encoder ");
    Serial.println(encoder);
    oldEncoder = encoder;
    int s = stateA;
    stateA = encoder;
    if (stateA < 0) stateA = 0;
    if(stateA > 4) stateA = 4;
    if(stateA != s) {
      TIMER4_init(); // частота дискретизации
      TIMER2_init(); // управление фильтром
      putScale(); // (24);      
      Serial.print("State ");
      Serial.println(stateA);
    }
  }
}

inline void checkSerialInput() {
  if(Serial.available()){
    char ch = Serial.read();
    if (ch >= '0' && ch <= '4') stateA = ch - '0';
  TIMER4_init(); // частота дискретизации
  TIMER2_init(); // управление фильтром
  putScale(); // (24);
  Serial.print(stateA);
  Serial.print(' ');
  Serial.print(stateF);
  Serial.println(" OK!");
  }
}

void setup()
{
  Serial.begin(9600);
  while (!Serial) { ; } // wait for serial port to connect.

  GPIOB_BASE->ODR = CTRL_MASK_DEFAULT;  // все управляющие HIGH
  GPIOB_BASE->CRL = 0x33333433;         // пины PB7-PB3, PB1-PB0 - выход, PB2 - вход (не исп.)
  GPIOB_BASE->CRH = 0x44433333;         // пины PB12-PB8 - выход, PB15-PB13 - не исп.
  GPIOB_BASE->ODR = CTRL_MASK_DEFAULT;  // 
  GPIOA_BASE->CRL = 0x4b4444b0;         // пины PA7-PA2 - вход (не исп.), PA0 - аналоговый вход, PA1 - выход таймера (для отладки)

  Lcd_Init();
  buffADC = ADCBuffer;
  buffFFT = ADCBuffer2;
  DMA_init();
  ADC_init();
  TIMER4_init(); // частота дискретизации
  TIMER2_init(); // управление фильтром
  TIMER1_init(); // энкодер
  
  for(int i = 0; i < size; i++) {
    buffADC[i] = 0;
    buffFFT[i] = 0;
  }
  putScale(); // (24);
}

void loop()
{
  while((DMA1_BASE->ISR & 2)== 0) { ; }
  int t0 = millis(); 
  touchScreen();
  ADC_init();
  resetDMA();
  TIMER4_init();
  for(int i = 0; i < size; i++) {
    x[i] = (buffFFT[i] - 2048)*w[i];
    y[i] = 0;
  }
  
  FFT(); 
  int ii = 0;
  int iii = 1320;
  for(int i = 1; i < 481; i++){
    int32_t s = 1002 - getLog6(x[i]*x[i] + x[1024-i]*x[1024-i] + y[i]*y[i] + y[1024-i]*y[1024-i]);
    if (ii < s) ii = s;
    if (iii > s) iii = s;
    if(s < 0) s = 0;
    if(s > 150) s = 150;
    H_line_b(0, i-1, s, 0x001f);
    H_line_r(s, i-1, 150-s, 0xf800);
  }
  int t1 = millis();

  static int nn = 0;
  if(!(nn%50)) {
    Serial.print(" Y: ");
    Serial.print(screenX); // Y+     PB13 connect to PB0 (RS - SDA)
    Serial.print(' ');
    Serial.print(" X: ");
    Serial.print(screenY); // X-     PB12 connect to PB1 (CS - SCL)
    Serial.print(" dt: ");
    Serial.print(t1-t0); 
    Serial.print(" min "); 
    Serial.print(ii); 
    Serial.print(" max "); 
    Serial.println(iii); 
  }
    nn++;
  if(nn > 10)   checkSerialInput();
  checkEncoder();

 // delay(5000);
}

Все это собрано на макетке для Меги. Да, первоначальные варианты отлаживал на уже спаянной макетке для проекта с видеокамерой, теперь на такой же макетке собрал новую схему:

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

Незанятая панелька в левой части платы - для фильтра. Для фильтра также нужно двухполярное питание, поэтому в самой левой части платы стоит преобразователь B0505, с которого я беру -5В. Справа снизу - стабилизатор. В какой-то фабричной MIDI-плате увидел, как сделано питание:

с разъема питание - сразу на мост (чтобы переполюсовка не приводила к печальным последствиям - обычный штыревой разъем питания ведь не стандартизован: "+" может быть как на центральном, так и на внешнем контакте).

после моста - стабилизатор на 5 В.

а уже после стабилизатора на 5 В там бал преобразователь на плюс-минус 12 В - для питания операционников.

В общем, что-то подобное решил сделать здесь.

"Кроватка" фильтра пока пустая. Вставил тонкую проволочку между входом и выходом фильтра - пока отлаживаю без него.

dimax
dimax аватар
Онлайн
Зарегистрирован: 25.12.2013

andriano, ну если что у меня пример обработчика энкодера есть в генераторах 3.x, работает отлично, настроен так, что-б на каждый щелчок выпадать в прерывание.

andriano
andriano аватар
Offline
Зарегистрирован: 20.06.2015

dimax, во-первых, это stm32, ф не avr, а во-вторых, - энкодер работает без прерываний - аппаратно на таймере. Есть у него такой режим работы. 

На avr и прерываниях у меня тоже стабильно 80 единиц за оборот (соответственно, можно 40 либо 20), там проблем нет.

dimax
dimax аватар
Онлайн
Зарегистрирован: 25.12.2013

andriano, вы не посмотрели что я вам написал. Версии 3.x -это на stm32, и обработчик там на таймере, именно таймер у меня организовывает прерывания, это сразу станет понятно посмотрев настройки в сетапе.

andriano
andriano аватар
Offline
Зарегистрирован: 20.06.2015

dimax, я посмотрел, но посмотрел только первый пост (на который, собственно, и указывала ссылка). Чуть позже посмотрю дальше.

 

А сейчас немного об ограничении частотного диапазона сигналов, подлежащих оцифровке.

Собственно, известно, что согласно теореме Котельникова-Найквиста-Шеннона в непрерывном сигнале могут быт восстановлены после оцифровки только частоты меньшие половины частоты дискретизации. А что же с теми, что выше? Они теряются? Нет, хуже! Они "отражаются" от половинной частоты дискретизации. Т.е. если частота дискретизации, скажем, 20 кГц, а в спектре была частота 13 кГц, то после оцифровки-восстановления в спектре появится частота 7 кГц, которой не было в исходном сигнале.

Это крайне нежелательно, т.к. заметность различных искажений различна. Многие виды искажений мало заметны на слух из-за эффекта маскировки. Скажем, если у нас есть частота 3 кГц сигнала сложной формы, то кроме 3 кГц в спектре присутствуют все кратные частоты: 6 кГц, 9 кГц, 12 кГц, 15 кГц, 18 кГц... В случае линейных или нелинейных искажений новые частоты у нас не появятся - останется тот же набор частот, только с немного измененным соотношением между ними. Такие искажения довольно слабо заметны на слух. А вот в случае оцифровки вместо частот 12 кГц, 15 кГц, 18 кГц у нас (частота дискретизации 20 кГц) появятся 8кГц, 5 кГц, 2 кГц, которых не было в исходном сигнале, а потому они будут весьма заметны. Кроме того, есть эффект маскировки, за счет которого частоты небольшой интенсивности могут полностью маскироваться (т.е. совсем не восприниматься слухом) более низкими частотами. Но низкие частоты не маскируются ничем. Т.е. частоты 2 (а скорее всего и 5) кГц будут сильно заметны даже при малой амплитуде.

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

И если мы делаем спектранализатор, логично, чтобы оцифровываемый им сигнал также пропускался через ФНЧ.

О самом ФНЧ расскажу позже, а сейчас просто приведу два фото.

Это спектр прямоугольного сигнала, снятый без фильтра. Чакстота исходного сигнала 1.1 кГц, а частота дискретизации 23.8 кГц, ее половина 11.9. Сам сигнал виден в виде "осциллограммы" белым цветом поверх спектра. Кавычки потому, что "осциллограмма" нарисована не линиями, а точками. Собственно, видно, что сигнал пропущее через ФВЧ, ну да речь не о том. В спектре должны присутствовать основной тон 1.1кГц, а также его нечетные гармоники: 3.3кГц, 5.5кГц, 7.7кГц, 9.9кГц, 12.1кГц, 14.3кГц...

Но на фото видно гораздо больше. Все лишнее - это и есть зеркальные частоты.

Совсем другое дело, если пропустить сигнал через фильтр четвертого порядка с частотой среза 10.3 кГц:

Конечно что-то осталось, но уже гораздо меньше. Чудес не бывает. И, кроме того, следует отметить, что динамический диапазон этого индикатора составляет 75 дБ, при том, что динамический диапазон 12-разрядного звука лишь 74 дБ (а как мы знаем, у stm32 12-разрядный АЦП). Т.е. малейшие шумы, включая шумы дискретизации, на индикаторе будут видны.

 

Arhat109-2
Offline
Зарегистрирован: 24.09.2015

Пасибки. Вот оказывается почему у меня не получилось оцифровав звук (речь) достоверно выделить форманты и "опознать голос". Хотели с сыном подавать команды роботу голосом, как-то сразу не задалось, а потом бросили. Сначала надо фильтровать, а потом оцифровывать, а не наоборот.. пасибки.

andriano
andriano аватар
Offline
Зарегистрирован: 20.06.2015

Ну хорошо, хоть кому-то понадобилось. А то, когда писал, очень сильно сомневался, стоит ли излагать хотя бы кусочки теории.

Arhat109-2
Offline
Зарегистрирован: 24.09.2015

Конечно, и больше чем уверен что не только мне. За долгую жизнь многа раз убеждался что всегда найдется N+1 читатель, которому это точно полезно, ИМХО, не стоит таким вопросом заморачиваться даже на "тютельку". :)

Кстати! Появилась мысля применить "программный фильтр", типа нашего восстановления засветки ИК-датчика. Вы можете попробовать на своей схеме, он прост: берем эталонный сигнал (напр. прямоугольник) и смотрим величину "отражений". Как видно по вашим картинкам - отражения распространяются от максиумов (и вообще от найденных частот) по определенному закону. Вот этот "закон" и вычитаем, из спектра в порядке ранжирования максиумов (и вообще найденных частот).

Т.е. смотрим, что +-1 гребенка меньше максиума на хх%, +-2-я гребенка .. думаю достаточно. Фиксим эти проценты, и получив спектр, сортируем его копию по убыванию амплитуды частот и в порядке копии "нормируем" спектр на эти проценты.

Попробуйте, мне наши микрофонные уши сейчас не проверить. (очень не скоро).

andriano
andriano аватар
Offline
Зарегистрирован: 20.06.2015

Arhat109-2, алгоритма не понял, но, думаю, Вы и сами в нем не разобрались.

Видите-ли, при "отражении" происходит необратимая потеря информации. Следовательно, никакие попытки восстановления сигнала в принципе не могут привести к успеху.

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

andriano
andriano аватар
Offline
Зарегистрирован: 20.06.2015

Насчет энкодера - выяснилась закономерность: допустим я задаю 5 позиций от 0 до 4. Если я кручу энкодер в пределах первого круга, он выдает 2 (1 - в зависимости от настроек) импульса на щелчок, а как только перехожу через границу (т.е. либо "ниже 0", либо "выше 4"), то он необратимо переходит в режим 4(2) импульса на щелчок. И побороть это мне пока не удалось.

dimax, к сожалению, воспользоваться Вашим кодом я не смог. Подозреваю, что Вы вообще нестандартно используете энкодер - лишь для того, чтобы он пропускал "лишние" импульсы, когда их 2 или 4 на щелчок, а подсчитывает положение энкодера сам камень через прерывания.

Т.е. я хочу вообще избавиться от прерываний, настроить энкодер по счету от 0 до 4 и чтобы положение энкодера я мог узнавать, читая TIMER4_BASE->CNT.

Arhat109-2
Offline
Зарегистрирован: 24.09.2015

andriano пишет:

Arhat109-2, алгоритма не понял, но, думаю, Вы и сами в нем не разобрались.

Видите-ли, при "отражении" происходит необратимая потеря информации. Следовательно, никакие попытки восстановления сигнала в принципе не могут привести к успеху.

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

Да, согласен. Для музыкального (не периодического) сигнала этот метод не подходит совсем.

dimax
dimax аватар
Онлайн
Зарегистрирован: 25.12.2013

andriano пишет:

dimax, к сожалению, воспользоваться Вашим кодом я не смог. Подозреваю, что Вы вообще нестандартно используете энкодер - лишь для того, чтобы он пропускал "лишние" импульсы, когда их 2 или 4 на щелчок, а подсчитывает положение энкодера сам камень через прерывания.Т.е. я хочу вообще избавиться от прерываний, настроить энкодер по счету от 0 до 4 и чтобы положение энкодера я мог узнавать, читая TIMER4_BASE->CNT.

А в чём проблема?  Сделать ARR=65535.

        TIMER4_BASE->CR1=(1<<2)|(1<<9);//CKD10 URS
     TIMER4_BASE->CCMR1=0xf1f1; //(1<<0)|(1<<8 );//cc1s, cc2s input mapped TI1/TI2
     TIMER4_BASE->CCER=(1<<1)|(1<<5)|(1<<0)|(1<<4);//Capture/Compare 1,2 output polarity
    TIMER4_BASE->SMCR=(1<<1)|(1<<0);//Encoder mode3(SMS bit)
   TIMER4_BASE->CNT=0; // с какого числа начинать счёт
  TIMER4_BASE->ARR=65535;//ограничение счёта 
  TIMER4_BASE->SR=0; 
TIMER4_BASE->EGR=1; 
TIMER4_BASE->CR1|=(1<<0);//запуск 

А падать в прерывание по достижении %ARR% или нет -это дело хозяйское :)

andriano
andriano аватар
Offline
Зарегистрирован: 20.06.2015

dimax, мне нужно не 65535, мне нужно 4 ровно! Иначе аппаратный энкодер не будет выполнять своих функций. Можно, конечно программно вычислят остаток по модулю 5, и даже при движении "вверх" у нас не будет наступато "переполнение" и связанное с ним изменение коэффициента пересчета. Но ведь при движении "вниз" по пересечению 0 у нас этот нежелательный эффект останется.

Так что вопрос не решен.

 

Да, в процессе экспериментов выяснил, что в самом низкочастотном дианазоне уровень шумов примерно на 10 дБ выше, чем в остальных:

Оказалось, время, за которое вводится массив отсчетов, больше, чем время FFT, и скетч начинал передавать данные на экран, когда еще шла оцифровка сигнала. Отсюда и помеха, имеющая вид широкополосного шума.

Написал - не начинать работу с экраном, пока не закончился ввод данных. Помогло.

dimax
dimax аватар
Онлайн
Зарегистрирован: 25.12.2013

andriano , Мы явно не понимаем друг друга.  Что вам надо то?  То вы пишите "чтобы положение энкодера я мог узнавать, читая TIMER4_BASE->CNT"  Я дал скетч, узнавайте через CNT. С нолём проблем нет, ставьте начало счёта на 32767, и пожалуйста, по 30 тыщ щелчков счёта в любую сторону. Читаете счётный регистр в лупе или где  надо, смотрите в какую сторону крутилось, счётчик опять скидываете на 32767. Совершенно не понимаю в чём тут может быть проблема.

andriano
andriano аватар
Offline
Зарегистрирован: 20.06.2015

dimax, хотелось полностью аппаратное решение, а Вы опять предлагаете программную обработку.

Ладно, решил пока проект на этом завершить.

Пока фотографировал, понял, почему приборы делают по 20 см в глубину, хотя корпус совершенно пустой. Я сделал 5 см и уже два раза только за время фотографирования он у меня упал. В общем, первоначально думал нарисовать и распечатать заднюю стенку на 3D-ринтере, но сейчас появилось желание сделать ее из толстого металла, миллиметров так 5, чтобы неглубоктй корпус не опрокидывался вперед при малейшем воздействии, например, перемещении сигнального кабеля.

Фото с генератора:

Фото с звуковым сигналом (MP3-плеер):

Схема:

Скетч:

#include "dma.h"
#include "MAX261.h"

//#define DEBUG

#define LCD_RD                  -1 // не исп.
#define LCD_WR              0x1000 // PB12 было 0x4000 // PB14   
#define LCD_RS              0x0800 // PB11 было 0x2000 // PB13   
#define LCD_CS              0x0002 // PB1  было 0x1000 // PB12          
#define LCD_RESET           0x0001 // PB0  было 0x0800 // PB11   
#define DATA_MASK           0x07f8 // PB3-PB10, данные передаются в разрядах: D0-PB3, D1-PB4, ... D7-PB10
#define CTRL_MASK_DEFAULT   0x1803 // все управляющие биты HIGH (WR | RS | CS | RESET)
#define CTRL_MASK_WR_RS_CS  0x0001 // маска для записи команд (RESET)
#define CTRL_MASK_WR_CS     0x0801 // маска для записи данных (RS | RESET)
#define AUDIO_INPUT         PA0    // аналоговый вход
#define size                1024   // длина буфера оцифровки

float x[size], y[size]; // действительные и мнимые части.
int16_t ADCBuffer[size], ADCBuffer2[size]; // буфера для поочередного ввода данных с АЦП через ПДП (другой буфер обрабатывается ЦПУ)
int16_t * buffADC, * buffFFT; // адреса буферов для ADC/DMA и CPU соответственно
byte stateA = 0; // частота оцифровки ADC: 0 - 11.9кГц, 1 - 23.8кГц, 3 - 47.6кГц, 4 - 95.2кГц, 5 - 142.9кГц
byte stateF = 0; // частота упр.фильтром(фильтра): 0 - 0.5МГц(5.0), 1 - 1МГц(10.0), 2 - 2МГц(20.0), 3 - 4МГц(40.0), 4 - 4МГц(56.3)
byte stateOsc = 0; // рисовать ли осциллограмму: 0 - нет, 1 - точками, 2 - линиями
const uint16_t dividorADC[5] = {3023, 1511, 755, 377, 251}; // делитель частоты 36 МГц до частоты дискретизации
const uint16_t dividorFilter[5] = {71, 35, 17, 8, 8}; // делитель частоты 36 МГц до управляющей частоты фильтра
const uint8_t filterFreqCode[5] = {26, 26, 26, 26, 0}; // управляющий код фильтра (соответствует отношениям {100,100,100,100,71})
const uint8_t stepScale[5] = {1, 1, 2, 5, 10};         // с каким шагом выводить оцифровку шкалы
const uint8_t maxScale[5] = {5, 10, 20, 40, 60};       // докуда оцифровывать шкалу
const uint32_t screenScale[5] = {87936, 43968, 21984, 10992, 7328}; // шаг шкалы на экране в 1/1024 пикселя

void Lcd_Write_Com(unsigned char d) { // вывод команды: опустить CS, RS и WR, вывести байт, поднять WR
  GPIOB_BASE->ODR = CTRL_MASK_WR_RS_CS | (d << 3);
  GPIOB_BASE->BSRR = LCD_WR;
}

inline void Lcd_Write_Data(unsigned char d) { // вывод данных: опустить CS и WR, вывести байт, поднять WR
  GPIOB_BASE->ODR = CTRL_MASK_WR_CS | (d << 3);
  GPIOB_BASE->BSRR = LCD_WR;
}

inline void Lcd_Write_Pixel(uint16_t d) { // вывод двух байтов данных из числа uint16_t
  GPIOB_BASE->ODR = CTRL_MASK_WR_CS | ((d & 0xff00) >> 5);
  GPIOB_BASE->BSRR = LCD_WR;
  GPIOB_BASE->ODR = CTRL_MASK_WR_CS | ((d & 0xff) << 3);
  GPIOB_BASE->BSRR = LCD_WR;
}

inline void Address_set(uint32_t x1, uint32_t y1, uint32_t x2, uint32_t y2) { // позиционирование на экране (оптимизировано)
  GPIOB_BASE->ODR = 0x0151; // команда 0x2a - Column Address Set, CS, RS, WR
  GPIOB_BASE->BSRR = LCD_WR;

  GPIOB_BASE->ODR = CTRL_MASK_WR_CS | ((x1 & 0xff00)>>5); //
  GPIOB_BASE->BSRR = LCD_WR;

  GPIOB_BASE->ODR = CTRL_MASK_WR_CS | ((x1 & 0xff) << 3); //
  GPIOB_BASE->BSRR = LCD_WR;

  GPIOB_BASE->ODR = CTRL_MASK_WR_CS | ((x2 & 0xff00)>>5); //
  GPIOB_BASE->BSRR = LCD_WR;

  GPIOB_BASE->ODR = CTRL_MASK_WR_CS | ((x2 & 0xff) << 3); //
  GPIOB_BASE->BSRR = LCD_WR;

  GPIOB_BASE->ODR = 0x0159; // команда 0x2b - Page Address Set
  GPIOB_BASE->BSRR = LCD_WR;

  GPIOB_BASE->ODR = CTRL_MASK_WR_CS | ((y1 & 0xff00)>>5); //
  GPIOB_BASE->BSRR = LCD_WR;

  GPIOB_BASE->ODR = CTRL_MASK_WR_CS | ((y1 & 0xff) << 3); //
  GPIOB_BASE->BSRR = LCD_WR;

  GPIOB_BASE->ODR = CTRL_MASK_WR_CS | ((y2 & 0xff00)>>5); //
  GPIOB_BASE->BSRR = LCD_WR;

  GPIOB_BASE->ODR = CTRL_MASK_WR_CS | ((y2 & 0xff) << 3); //
  GPIOB_BASE->BSRR = LCD_WR;

  GPIOB_BASE->ODR = 0x161; // команда 0x2c - Mempry Write: переключаем на режим ввода данных (пикселей)
  GPIOB_BASE->BSRR = LCD_WR;
}

void Lcd_Init(void) { // ILI9481 вызываем один раз, поэтому по скорости не оптимизируем
  GPIOB_BASE->BSRR = LCD_RESET;
  delay(5); 
  GPIOB_BASE->BRR = LCD_RESET;
  delay(15);
  GPIOB_BASE->BSRR = LCD_RESET;
  delay(15);

  Lcd_Write_Com(0x11);
  delay(20);
  Lcd_Write_Com(0xD0);
  Lcd_Write_Data(0x07);
  Lcd_Write_Data(0x42);
  Lcd_Write_Data(0x18);
  
  Lcd_Write_Com(0xD1);
  Lcd_Write_Data(0x00);
  Lcd_Write_Data(0x07);
  Lcd_Write_Data(0x10);
  
  Lcd_Write_Com(0xD2);
  Lcd_Write_Data(0x01);
  Lcd_Write_Data(0x02);
  
  Lcd_Write_Com(0xC0);
  Lcd_Write_Data(0x10);
  Lcd_Write_Data(0x3B);
  Lcd_Write_Data(0x00);
  Lcd_Write_Data(0x02);
  Lcd_Write_Data(0x11);
  
  Lcd_Write_Com(0xC5);
  Lcd_Write_Data(0x03);
  Lcd_Write_Com(0x36);
  Lcd_Write_Data(0x0A);
  
  Lcd_Write_Com(0x3A);
  Lcd_Write_Data(0x55); 
  delay(120); 
				
  Lcd_Write_Com(0x29);    //Display ON
  Lcd_Write_Com(0x2c);    // Memory Write
}

void H_line(uint32_t x, uint32_t y, uint32_t l, uint32_t c) {	// на самом деле вертикальной, т.к. экран у нас на боку
  Address_set(x, y, x+l-1, y);
  for(uint32_t i=0; i<l; i++)
  {
      GPIOB_BASE->ODR = CTRL_MASK_WR_CS | ((c >> 5) & DATA_MASK);
      GPIOB_BASE->BSRR = LCD_WR;
      GPIOB_BASE->ODR = CTRL_MASK_WR_CS | ((c << 3) & DATA_MASK);
      GPIOB_BASE->BSRR = LCD_WR;
  }
}

void V_line(uint32_t x, uint32_t y, uint32_t w, uint32_t c) { // на самом деле горизонтальной, т.к. экран у нас на боку  
  Address_set(x, y, x, y+w-1);
  for(uint32_t i=0; i<w; i++)
  {
      GPIOB_BASE->ODR = CTRL_MASK_WR_CS | ((c >> 5) & DATA_MASK);
      GPIOB_BASE->BSRR = LCD_WR;
      GPIOB_BASE->ODR = CTRL_MASK_WR_CS | ((c << 3) & DATA_MASK);
      GPIOB_BASE->BSRR = LCD_WR;
  }
}

const float w[size] = { // массив весовых коэффициентов функции окна для БПФ
0.00153398, 0.00460193, 0.00766983, 0.01073766, 0.01380539, 0.01687299, 0.01994043, 0.02300768, 0.02607472, 0.02914151, 0.03220803, 0.03527424, 0.03834012, 0.04140564, 0.04447077, 0.04753548, 
0.05059975, 0.05366354, 0.05672682, 0.05978957, 0.06285176, 0.06591335, 0.06897433, 0.07203465, 0.07509430, 0.07815324, 0.08121145, 0.08426889, 0.08732554, 0.09038136, 0.09343634, 0.09649043, 
0.09954362, 0.10259587, 0.10564715, 0.10869744, 0.11174671, 0.11479493, 0.11784206, 0.12088809, 0.12393298, 0.12697670, 0.13001922, 0.13306053, 0.13610058, 0.13913934, 0.14217680, 0.14521292, 
0.14824768, 0.15128104, 0.15431297, 0.15734346, 0.16037246, 0.16339995, 0.16642590, 0.16945029, 0.17247308, 0.17549425, 0.17851377, 0.18153161, 0.18454774, 0.18756213, 0.19057475, 0.19358559, 
0.19659460, 0.19960176, 0.20260704, 0.20561041, 0.20861185, 0.21161133, 0.21460881, 0.21760427, 0.22059769, 0.22358903, 0.22657826, 0.22956537, 0.23255031, 0.23553306, 0.23851359, 0.24149189, 
0.24446790, 0.24744162, 0.25041301, 0.25338204, 0.25634868, 0.25931292, 0.26227471, 0.26523403, 0.26819086, 0.27114516, 0.27409691, 0.27704608, 0.27999264, 0.28293657, 0.28587783, 0.28881641, 
0.29175226, 0.29468537, 0.29761571, 0.30054324, 0.30346795, 0.30638980, 0.30930876, 0.31222481, 0.31513793, 0.31804808, 0.32095523, 0.32385937, 0.32676045, 0.32965846, 0.33255337, 0.33544515, 
0.33833377, 0.34121920, 0.34410143, 0.34698041, 0.34985613, 0.35272856, 0.35559766, 0.35846342, 0.36132581, 0.36418479, 0.36704035, 0.36989245, 0.37274107, 0.37558618, 0.37842775, 0.38126577, 
0.38410020, 0.38693101, 0.38975817, 0.39258167, 0.39540148, 0.39821756, 0.40102990, 0.40383846, 0.40664322, 0.40944415, 0.41224123, 0.41503442, 0.41782372, 0.42060907, 0.42339047, 0.42616789, 
0.42894129, 0.43171066, 0.43447596, 0.43723717, 0.43999427, 0.44274723, 0.44549602, 0.44824061, 0.45098099, 0.45371712, 0.45644898, 0.45917655, 0.46189979, 0.46461869, 0.46733321, 0.47004333, 
0.47274903, 0.47545028, 0.47814706, 0.48083933, 0.48352708, 0.48621028, 0.48888890, 0.49156292, 0.49423231, 0.49689705, 0.49955711, 0.50221247, 0.50486311, 0.50750899, 0.51015010, 0.51278640, 
0.51541788, 0.51804450, 0.52066625, 0.52328310, 0.52589503, 0.52850200, 0.53110400, 0.53370100, 0.53629298, 0.53887991, 0.54146177, 0.54403853, 0.54661017, 0.54917666, 0.55173799, 0.55429412, 
0.55684504, 0.55939071, 0.56193112, 0.56446624, 0.56699605, 0.56952052, 0.57203963, 0.57455336, 0.57706167, 0.57956456, 0.58206199, 0.58455394, 0.58704039, 0.58952132, 0.59199669, 0.59446650, 
0.59693071, 0.59938930, 0.60184225, 0.60428953, 0.60673113, 0.60916701, 0.61159716, 0.61402156, 0.61644017, 0.61885299, 0.62125998, 0.62366112, 0.62605639, 0.62844577, 0.63082923, 0.63320676, 
0.63557832, 0.63794390, 0.64030348, 0.64265703, 0.64500454, 0.64734597, 0.64968131, 0.65201053, 0.65433362, 0.65665055, 0.65896129, 0.66126584, 0.66356416, 0.66585623, 0.66814204, 0.67042156, 
0.67269477, 0.67496165, 0.67722217, 0.67947632, 0.68172407, 0.68396541, 0.68620031, 0.68842875, 0.69065071, 0.69286617, 0.69507511, 0.69727751, 0.69947334, 0.70166259, 0.70384524, 0.70602126, 
0.70819064, 0.71035335, 0.71250937, 0.71465869, 0.71680128, 0.71893712, 0.72106620, 0.72318849, 0.72530397, 0.72741263, 0.72951444, 0.73160938, 0.73369744, 0.73577859, 0.73785281, 0.73992010, 
0.74198041, 0.74403374, 0.74608007, 0.74811938, 0.75015165, 0.75217685, 0.75419498, 0.75620600, 0.75820991, 0.76020668, 0.76219630, 0.76417874, 0.76615399, 0.76812203, 0.77008284, 0.77203640, 
0.77398269, 0.77592170, 0.77785340, 0.77977779, 0.78169483, 0.78360452, 0.78550683, 0.78740175, 0.78928925, 0.79116933, 0.79304196, 0.79490713, 0.79676481, 0.79861499, 0.80045766, 0.80229280, 
0.80412038, 0.80594039, 0.80775282, 0.80955764, 0.81135485, 0.81314441, 0.81492633, 0.81670057, 0.81846713, 0.82022598, 0.82197712, 0.82372051, 0.82545615, 0.82718403, 0.82890411, 0.83061640, 
0.83232087, 0.83401750, 0.83570628, 0.83738720, 0.83906024, 0.84072537, 0.84238260, 0.84403190, 0.84567325, 0.84730664, 0.84893206, 0.85054948, 0.85215890, 0.85376030, 0.85535366, 0.85693898, 
0.85851622, 0.86008539, 0.86164646, 0.86319942, 0.86474426, 0.86628095, 0.86780950, 0.86932987, 0.87084206, 0.87234606, 0.87384184, 0.87532940, 0.87680872, 0.87827979, 0.87974259, 0.88119711, 
0.88264334, 0.88408126, 0.88551086, 0.88693212, 0.88834503, 0.88974959, 0.89114576, 0.89253356, 0.89391295, 0.89528392, 0.89664647, 0.89800058, 0.89934624, 0.90068343, 0.90201214, 0.90333237, 
0.90464409, 0.90594730, 0.90724198, 0.90852812, 0.90980571, 0.91107473, 0.91233518, 0.91358705, 0.91483031, 0.91606497, 0.91729100, 0.91850839, 0.91971715, 0.92091724, 0.92210867, 0.92329142, 
0.92446547, 0.92563083, 0.92678747, 0.92793539, 0.92907458, 0.93020502, 0.93132671, 0.93243963, 0.93354377, 0.93463913, 0.93572569, 0.93680344, 0.93787238, 0.93893248, 0.93998375, 0.94102618, 
0.94205974, 0.94308444, 0.94410026, 0.94510719, 0.94610523, 0.94709437, 0.94807459, 0.94904588, 0.95000825, 0.95096167, 0.95190614, 0.95284165, 0.95376819, 0.95468575, 0.95559433, 0.95649392, 
0.95738450, 0.95826607, 0.95913862, 0.96000215, 0.96085663, 0.96170208, 0.96253847, 0.96336580, 0.96418406, 0.96499325, 0.96579336, 0.96658437, 0.96736629, 0.96813910, 0.96890280, 0.96965739, 
0.97040284, 0.97113916, 0.97186634, 0.97258437, 0.97329325, 0.97399296, 0.97468351, 0.97536489, 0.97603708, 0.97670009, 0.97735390, 0.97799851, 0.97863392, 0.97926012, 0.97987710, 0.98048486, 
0.98108339, 0.98167269, 0.98225274, 0.98282355, 0.98338511, 0.98393741, 0.98448046, 0.98501423, 0.98553874, 0.98605396, 0.98655991, 0.98705657, 0.98754394, 0.98802202, 0.98849079, 0.98895026, 
0.98940043, 0.98984128, 0.99027281, 0.99069503, 0.99110791, 0.99151147, 0.99190570, 0.99229059, 0.99266614, 0.99303235, 0.99338921, 0.99373672, 0.99407488, 0.99440368, 0.99472312, 0.99503320, 
0.99533391, 0.99562526, 0.99590723, 0.99617983, 0.99644305, 0.99669690, 0.99694136, 0.99717644, 0.99740213, 0.99761844, 0.99782535, 0.99802287, 0.99821100, 0.99838974, 0.99855907, 0.99871901, 
0.99886955, 0.99901069, 0.99914242, 0.99926475, 0.99937767, 0.99948119, 0.99957530, 0.99966000, 0.99973529, 0.99980117, 0.99985764, 0.99990470, 0.99994235, 0.99997059, 0.99998941, 0.99999882, 
0.99999882, 0.99998941, 0.99997059, 0.99994235, 0.99990470, 0.99985764, 0.99980117, 0.99973529, 0.99966000, 0.99957530, 0.99948119, 0.99937767, 0.99926475, 0.99914242, 0.99901069, 0.99886955, 
0.99871901, 0.99855907, 0.99838974, 0.99821100, 0.99802287, 0.99782535, 0.99761844, 0.99740213, 0.99717644, 0.99694136, 0.99669690, 0.99644305, 0.99617983, 0.99590723, 0.99562526, 0.99533391, 
0.99503320, 0.99472312, 0.99440368, 0.99407488, 0.99373672, 0.99338921, 0.99303235, 0.99266614, 0.99229059, 0.99190570, 0.99151147, 0.99110791, 0.99069503, 0.99027281, 0.98984128, 0.98940043, 
0.98895026, 0.98849079, 0.98802202, 0.98754394, 0.98705657, 0.98655991, 0.98605396, 0.98553874, 0.98501423, 0.98448046, 0.98393741, 0.98338511, 0.98282355, 0.98225274, 0.98167269, 0.98108339, 
0.98048486, 0.97987710, 0.97926012, 0.97863392, 0.97799851, 0.97735390, 0.97670009, 0.97603708, 0.97536489, 0.97468351, 0.97399296, 0.97329325, 0.97258437, 0.97186634, 0.97113916, 0.97040284, 
0.96965739, 0.96890280, 0.96813910, 0.96736629, 0.96658437, 0.96579336, 0.96499325, 0.96418406, 0.96336580, 0.96253847, 0.96170208, 0.96085663, 0.96000215, 0.95913862, 0.95826607, 0.95738450, 
0.95649392, 0.95559433, 0.95468575, 0.95376819, 0.95284165, 0.95190614, 0.95096167, 0.95000825, 0.94904588, 0.94807459, 0.94709437, 0.94610523, 0.94510719, 0.94410026, 0.94308444, 0.94205974, 
0.94102618, 0.93998375, 0.93893248, 0.93787238, 0.93680344, 0.93572569, 0.93463913, 0.93354377, 0.93243963, 0.93132671, 0.93020502, 0.92907458, 0.92793539, 0.92678747, 0.92563083, 0.92446547, 
0.92329142, 0.92210867, 0.92091724, 0.91971715, 0.91850839, 0.91729100, 0.91606497, 0.91483031, 0.91358705, 0.91233518, 0.91107473, 0.90980571, 0.90852812, 0.90724198, 0.90594730, 0.90464409, 
0.90333237, 0.90201214, 0.90068343, 0.89934624, 0.89800058, 0.89664647, 0.89528392, 0.89391295, 0.89253356, 0.89114576, 0.88974959, 0.88834503, 0.88693212, 0.88551086, 0.88408126, 0.88264334, 
0.88119711, 0.87974259, 0.87827979, 0.87680872, 0.87532940, 0.87384184, 0.87234606, 0.87084206, 0.86932987, 0.86780950, 0.86628095, 0.86474426, 0.86319942, 0.86164646, 0.86008539, 0.85851622, 
0.85693898, 0.85535366, 0.85376030, 0.85215890, 0.85054948, 0.84893206, 0.84730664, 0.84567325, 0.84403190, 0.84238260, 0.84072537, 0.83906024, 0.83738720, 0.83570628, 0.83401750, 0.83232087, 
0.83061640, 0.82890411, 0.82718403, 0.82545615, 0.82372051, 0.82197712, 0.82022598, 0.81846713, 0.81670057, 0.81492633, 0.81314441, 0.81135485, 0.80955764, 0.80775282, 0.80594039, 0.80412038, 
0.80229280, 0.80045766, 0.79861499, 0.79676481, 0.79490713, 0.79304196, 0.79116933, 0.78928925, 0.78740175, 0.78550683, 0.78360452, 0.78169483, 0.77977779, 0.77785340, 0.77592170, 0.77398269, 
0.77203640, 0.77008284, 0.76812203, 0.76615399, 0.76417874, 0.76219630, 0.76020668, 0.75820991, 0.75620600, 0.75419498, 0.75217685, 0.75015165, 0.74811938, 0.74608007, 0.74403374, 0.74198041, 
0.73992010, 0.73785281, 0.73577859, 0.73369744, 0.73160938, 0.72951444, 0.72741263, 0.72530397, 0.72318849, 0.72106620, 0.71893712, 0.71680128, 0.71465869, 0.71250937, 0.71035335, 0.70819064, 
0.70602126, 0.70384524, 0.70166259, 0.69947334, 0.69727751, 0.69507511, 0.69286617, 0.69065071, 0.68842875, 0.68620031, 0.68396541, 0.68172407, 0.67947632, 0.67722217, 0.67496165, 0.67269477, 
0.67042156, 0.66814204, 0.66585623, 0.66356416, 0.66126584, 0.65896129, 0.65665055, 0.65433362, 0.65201053, 0.64968131, 0.64734597, 0.64500454, 0.64265703, 0.64030348, 0.63794390, 0.63557832, 
0.63320676, 0.63082923, 0.62844577, 0.62605639, 0.62366112, 0.62125998, 0.61885299, 0.61644017, 0.61402156, 0.61159716, 0.60916701, 0.60673113, 0.60428953, 0.60184225, 0.59938930, 0.59693071, 
0.59446650, 0.59199669, 0.58952132, 0.58704039, 0.58455394, 0.58206199, 0.57956456, 0.57706167, 0.57455336, 0.57203963, 0.56952052, 0.56699605, 0.56446624, 0.56193112, 0.55939071, 0.55684504, 
0.55429412, 0.55173799, 0.54917666, 0.54661017, 0.54403853, 0.54146177, 0.53887991, 0.53629298, 0.53370100, 0.53110400, 0.52850200, 0.52589503, 0.52328310, 0.52066625, 0.51804450, 0.51541788, 
0.51278640, 0.51015010, 0.50750899, 0.50486311, 0.50221247, 0.49955711, 0.49689705, 0.49423231, 0.49156292, 0.48888890, 0.48621028, 0.48352708, 0.48083933, 0.47814706, 0.47545028, 0.47274903, 
0.47004333, 0.46733321, 0.46461869, 0.46189979, 0.45917655, 0.45644898, 0.45371712, 0.45098099, 0.44824061, 0.44549602, 0.44274723, 0.43999427, 0.43723717, 0.43447596, 0.43171066, 0.42894129, 
0.42616789, 0.42339047, 0.42060907, 0.41782372, 0.41503442, 0.41224123, 0.40944415, 0.40664322, 0.40383846, 0.40102990, 0.39821756, 0.39540148, 0.39258167, 0.38975817, 0.38693101, 0.38410020, 
0.38126577, 0.37842775, 0.37558618, 0.37274107, 0.36989245, 0.36704035, 0.36418479, 0.36132581, 0.35846342, 0.35559766, 0.35272856, 0.34985613, 0.34698041, 0.34410143, 0.34121920, 0.33833377, 
0.33544515, 0.33255337, 0.32965846, 0.32676045, 0.32385937, 0.32095523, 0.31804808, 0.31513793, 0.31222481, 0.30930876, 0.30638980, 0.30346795, 0.30054324, 0.29761571, 0.29468537, 0.29175226, 
0.28881641, 0.28587783, 0.28293657, 0.27999264, 0.27704608, 0.27409691, 0.27114516, 0.26819086, 0.26523403, 0.26227471, 0.25931292, 0.25634868, 0.25338204, 0.25041301, 0.24744162, 0.24446790, 
0.24149189, 0.23851359, 0.23553306, 0.23255031, 0.22956537, 0.22657826, 0.22358903, 0.22059769, 0.21760427, 0.21460881, 0.21161133, 0.20861185, 0.20561041, 0.20260704, 0.19960176, 0.19659460, 
0.19358559, 0.19057475, 0.18756213, 0.18454774, 0.18153161, 0.17851377, 0.17549425, 0.17247308, 0.16945029, 0.16642590, 0.16339995, 0.16037246, 0.15734346, 0.15431297, 0.15128104, 0.14824768, 
0.14521292, 0.14217680, 0.13913934, 0.13610058, 0.13306053, 0.13001922, 0.12697670, 0.12393298, 0.12088809, 0.11784206, 0.11479493, 0.11174671, 0.10869744, 0.10564715, 0.10259587, 0.09954362, 
0.09649043, 0.09343634, 0.09038136, 0.08732554, 0.08426889, 0.08121145, 0.07815324, 0.07509430, 0.07203465, 0.06897433, 0.06591335, 0.06285176, 0.05978957, 0.05672682, 0.05366354, 0.05059975, 
0.04753548, 0.04447077, 0.04140564, 0.03834012, 0.03527424, 0.03220803, 0.02914151, 0.02607472, 0.02300768, 0.01994043, 0.01687299, 0.01380539, 0.01073766, 0.00766983, 0.00460193, 0.00153398};

inline void FFT() { // 1024 отсчета
    int32_t i,i1,j,k,l,l1,l2;
    float c1,c2,tx,ty,t1,t2,u1,u2,z;

    /* Do the bit reversal */
    j = 0;
    for (i = 0; i < 1023; i++) {
        if (i < j) {
            tx = x[i];
            ty = y[i];
            x[i] = x[j];
            y[i] = y[j];
            x[j] = tx;
            y[j] = ty;
        }
        k = 512;
        while (k <= j) {
            j -= k;
            k >>= 1;
        }
        j += k;
    }

    /* Compute the FFT */
    c1 = -1.0;
    c2 = 0.0;
    l2 = 1;
    for (l = 0; l < 10; l++) {
        l1 = l2;
        l2 <<= 1;
        u1 = 1.0;
        u2 = 0.0;
        for (j = 0; j < l1; j++) {
            for (i = j; i < 1024; i += l2) {
                i1 = i + l1;
                t1 = u1 * x[i1] - u2 * y[i1];
                t2 = u1 * y[i1] + u2 * x[i1];
                x[i1] = x[i] - t1;
                y[i1] = y[i] - t2;
                x[i] += t1;
                y[i] += t2;
            }
            z =  u1 * c1 - u2 * c2;
            u2 = u1 * c2 + u2 * c1;
            u1 = z;
        }
        c2 = -sqrt((1.0 - c1) / 2.0);
        c1 = sqrt((1.0 + c1) / 2.0);
    }

    /* Calculate the absolute value */
//        for (i = 0; i < 1024; i++) {
//            y[i] = (y[i]*y[i] + x[i]*x[i]); // /1024;
//        }
}

void DMA_init(void) {
    RCC_BASE->AHBENR |= 1; //DMA1EN - вкл.тактирования DMA1, все остальное (таймер, порты, ADC, RCC_PCLK2_Div6) stm32duino по умолчанию уже включило
// DMA1->CCR1: MEM2MEM=0, PL=2, MSIZE=01, PSIZE=01, MINC=1, PINC=0, CIRC=0, DIR=0, TEIE=0, HTIE=0, TCIE=0, EN=1 
    DMA1_BASE->CCR1 |= 0x2000; // PL=2 (bits 13-12) - high
    DMA1_BASE->CCR1 |= 0x0400; // MSIZE=01 (bits 11-10) - 16 bits
    DMA1_BASE->CCR1 |= 0x0100; // PIZE=01 (bits 9-8) - 16 bits
    DMA1_BASE->CCR1 |= 0x0080; // MINC (bit 7) инкрементировать адреса в памяти
    DMA1_BASE->CNDTR1 = size;  // сколько сэмплов передать (read only when channel disabled)
    DMA1_BASE->CPAR1 = (uint32_t)&ADC1_BASE->DR; // читать из ADC1
    DMA1_BASE->CMAR1 = (uint32_t)buffADC; // куда писать
    DMA1_BASE->CCR1 |= 1; // EN (bit 0) 0 включаем
}

void ADC_init() {
    int a = analogRead(AUDIO_INPUT); // на всякий случай, чтобы гарантированно провести калибровку АЦП
// ADC1->CR1: AWDEN=0, JAWDEN=0, DUALMOD=0(Independed), DISCNUM=0, JDISCEN=0, DISCEN=0, JAUTO=0, AWDSGL=0, SCAN=0, JEOCIE=0, AWDIE=0, EOCIE=0, AWDCH=0
// ADC1->CR2: TSVREFE=0, SWSTART=0, JSWSTART=0, EXTTRIG=1, EXTCEL=3(Tim2-CC2), JEXTTRIG=0, JEXTCEL=0, ALIGN=0, DMA=1, RSTCAL=0, CAL=0, CONT=0, ADON=1
// ADC1->SQR1.l=0 (one channel), SQR3.SQ1=0 (first channel)
    ADC1_BASE->CR2 |= 0x00100000; // EXTTRIG (bit 20)
////////  ADC1_BASE->CR2 |= 0x00000002; // CONT=1 (bit 1) - это непрерывное преобразование без таймера ???
    ADC1_BASE->CR2 &= 0xfff1ffff;
//    ADC1_BASE->CR2 |= 0x00060000; // EXTCEL=3 (bit 19-17) Tim2CC2
    ADC1_BASE->CR2 |= 0x000a0000; // EXTCEL=5 (bit 19-17) Tim4CC4
    ADC1_BASE->CR2 |= 0x00000100; // DMA (bit 8)
    ADC1_BASE->CR2 |= 1; // ADON
}

void TIMER2_init() { // channel 2 - 4 MHz for filter
// default by stm32duino: TIMER2->CR1.ARPE=1, CR1.CEN=1, CCMR1/2=[0x68], DMAR=0x81, EGR.CC2G=1, EGR.UG=1 (?), CCER.CC2E=1
    TIMER2_BASE->ARR = dividorFilter[stateF]; // 8; // 36000k/9=4000k - верхняя частота для фильтра
    TIMER2_BASE->CCR2 = (dividorFilter[stateF] + 1)/2; // 4; // 8/2=4
    TIMER2_BASE->CCER = 0x0010;  // CC2 enable
#ifdef DEBUG
    Serial.print("Timer2 ");
    Serial.println(36000.0/(TIMER2_BASE->ARR + 1));
#endif    
}

void TIMER4_init() { // channel 4 - 48 kHz to ADC
// default by stm32duino: TIMER4->CR1.ARPE=1, CR1.CEN=1, CCMR1/2=[0x68], DMAR=0x81, EGR.CC2G=1, EGR.UG=1 (?), CCER.CC2E=1
    TIMER4_BASE->ARR = dividorADC[stateA]; // 251; //// 749; // 36000k/750=48k == делитель=756, частота 47.619 - чтобы не было разностных частот (16к)
    TIMER4_BASE->CCR4 = (dividorADC[stateA] + 1)/2; // 126; // 374; // 750/2=375
    TIMER4_BASE->CCER = 0x1000;  // CC4 enable
#ifdef DEBUG
    Serial.print("Timer4 ");
    Serial.println(36000.0/(TIMER4_BASE->ARR + 1));
#endif    
}

void TIMER1_init() { // настраиваем для использования энкодера на ногах PA8 и PA9
  TIMER1_BASE->CR1 = 0; // Временно выключаем таймер
//  TIMER1_BASE->CR1 = 0x0204; // CKD=01
  TIMER1_BASE->SMCR  = 0x0001; // TIMER_SMCR_SMS_ENCODER2;
//  TIMER4_BASE->EGR=1; // Reinitialize the counter
  TIMER1_BASE->CCMR1 = 0x0101; // TIMER_CCMR1_IC1F | TIMER_CCMR1_IC2F | TIMER_CCMR1_CC1S_INPUT_TI1 | TIMER_CCMR1_CC2S_INPUT_TI2;
//  TIMER1_BASE->CCMR1 |= TIMER_CCMR1_CC1S_INPUT_TI1 | TIMER_CCMR1_CC2S_INPUT_TI2;
  TIMER1_BASE->CCMR2 = 0; ///???///
  TIMER1_BASE->CCER = 0;
  TIMER1_BASE->CNT = stateA*2;
  TIMER1_BASE->PSC = 0;
  TIMER1_BASE->ARR = 9;
  TIMER1_BASE->CCR1 = 0;
  TIMER1_BASE->CCR2 = 0;
  TIMER1_BASE->CCR3 = 0;
  TIMER1_BASE->CCR4 = 0;
  TIMER1_BASE->DMAR = 0;
  TIMER1_BASE->CR1 |= 1; // Включаем таймер(0x0001), DIR(0x0010) - бит направления вращения 
}

void inline resetDMA(){
  DMA1_BASE->CCR1 &= 0xfffffffe; // EN (bit 0)
  DMA1_BASE->CNDTR1 = size;
  DMA1_BASE->IFCR = 0x0000000f;
  int16_t * tmp = buffFFT;
  buffFFT = buffADC;
  buffADC = tmp;
  DMA1_BASE->CMAR1 = (uint32_t)buffADC; // куда писать
  DMA1_BASE->CCR1 |= 1;
}

union ct {
  float f;
  uint32_t d;
}; 

int getLog6(float b) {
  ct c;
  c.f = b;
  if     ((c.d & 0x007fffff) >= 6558192) return ((c.d >> 23) & 0xff)*6 + 5;
  else if((c.d & 0x007fffff) >= 4927477) return ((c.d >> 23) & 0xff)*6 + 4;
  else if((c.d & 0x007fffff) >= 3474675) return ((c.d >> 23) & 0xff)*6 + 3;
  else if((c.d & 0x007fffff) >= 2180375) return ((c.d >> 23) & 0xff)*6 + 2;
  else if((c.d & 0x007fffff) >= 1027286) return ((c.d >> 23) & 0xff)*6 + 1;
  else                                   return ((c.d >> 23) & 0xff)*6;

}

#define fontdatatype uint8_t
extern fontdatatype SmallFont[];

void writeLetter_x2(uint32_t x, uint32_t y, uint16_t color, uint8_t ch) {
  Address_set(x, y, x+15, y+9);
  int offs = (ch-32)*5 + 4;
  for(int j = 0; j < 5; j++) {
   for(int k = 0; k < 2; k++)
    for(int i = 0; i < 8; i++) {
      if(SmallFont[offs+j] & (1 << i)) {
        Lcd_Write_Pixel(color);
        Lcd_Write_Pixel(color);
      } else {
        Lcd_Write_Pixel(0);
        Lcd_Write_Pixel(0);
      }
    }
  }
}

void writeNumber_2(int y, int n) { // написать 1-2-значное число n с центром по координате y
  if(n < 10) {
    writeLetter_x2(303, y - 4, 0xffff, 48 + n);
  } else {
    writeLetter_x2(303, y - 12, 0xffff, 48 + n/10);
    writeLetter_x2(303, y +  0, 0xffff, 48 + n%10);
  }
}

void putScale() {
  Address_set(300, 0, 319, 479);
  for (int i = 0; i < 20*480; i++) {
    Lcd_Write_Pixel(0);
  }
  V_line(300, 0, 480, 0xffff);
  for(int i = 0; i <= maxScale[stateA]; i++) {
    H_line(300, i*screenScale[stateA]/1024 /*512.0/m*/, 4, 0xffff);
    if((i > 0) && ((i % stepScale[stateA]) == 0)) {
      writeNumber_2(i*screenScale[stateA]/1024 /*512.0/m*/, i);
    }
  }
}

#define BLUE_LO 0x00f8
#define LIGHT_BLUE_LO 0x04f8
#define RED_HI 0x07c0
#define LIGHT_RED_LO 0x0400
int screenX, screenY;

#define TWO_PIXELS(x,y)                       \
      GPIOB_BASE->ODR = CTRL_MASK_WR_CS;      \
      GPIOB_BASE->BSRR = LCD_WR;              \
      GPIOB_BASE->ODR = CTRL_MASK_WR_CS | x;  \
      GPIOB_BASE->BSRR = LCD_WR;              \
      GPIOB_BASE->ODR = CTRL_MASK_WR_CS;      \
      GPIOB_BASE->BSRR = LCD_WR;              \
      GPIOB_BASE->ODR = CTRL_MASK_WR_CS | y;  \
      GPIOB_BASE->BSRR = LCD_WR;
#define TWO_PIXELS_RED                            \
      GPIOB_BASE->ODR = CTRL_MASK_WR_CS | RED_HI; \
      GPIOB_BASE->BSRR = LCD_WR;                  \
      GPIOB_BASE->ODR = CTRL_MASK_WR_CS;          \
      GPIOB_BASE->BSRR = LCD_WR;                  \
      GPIOB_BASE->ODR = CTRL_MASK_WR_CS | RED_HI; \
      GPIOB_BASE->BSRR = LCD_WR;                  \
      GPIOB_BASE->ODR = CTRL_MASK_WR_CS;          \
      GPIOB_BASE->BSRR = LCD_WR;
#define TWO_PIXELS_LIGHT_RED                            \
      GPIOB_BASE->ODR = CTRL_MASK_WR_CS | RED_HI;       \
      GPIOB_BASE->BSRR = LCD_WR;                        \
      GPIOB_BASE->ODR = CTRL_MASK_WR_CS;                \
      GPIOB_BASE->BSRR = LCD_WR;                        \
      GPIOB_BASE->ODR = CTRL_MASK_WR_CS | RED_HI;       \
      GPIOB_BASE->BSRR = LCD_WR;                        \
      GPIOB_BASE->ODR = CTRL_MASK_WR_CS | LIGHT_RED_LO; \
      GPIOB_BASE->BSRR = LCD_WR;

void H_line_b(int x, int y, int s) { // рисование синей линии с полосками каждый 12-й пиксель (считая через 3 дБ по 2 пикселя на 0.5 дБ)
  int s0 = s/6; // (0, i-1, s*2, 0x001f)
  int s1 = s%6;

  Address_set(x, y, x+s*2-1, y);
  for(uint32_t i = 0; i < s0; i++)
  {
    TWO_PIXELS(BLUE_LO, BLUE_LO)
    TWO_PIXELS(BLUE_LO, BLUE_LO)
    TWO_PIXELS(BLUE_LO, BLUE_LO)
    TWO_PIXELS(BLUE_LO, BLUE_LO)
    TWO_PIXELS(BLUE_LO, BLUE_LO)
    TWO_PIXELS(BLUE_LO, LIGHT_BLUE_LO)
  }
  if (s1 > 0)
    for(uint32_t i = 0; i < s1; i++)
    {
      TWO_PIXELS(BLUE_LO, BLUE_LO)
    }
}

void H_line_r(int x, int y, int s) { // рисование красной линии с полосками каждый 12-й пиксель (считая через 3 дБ по 2 пикселя на 0.5 дБ)
  int s0 = s/6; // (0, i-1, s*2, 0xf800)
  int s1 = s%6;

  Address_set(x*2, y, x*2+s*2-1, y);
  if (s1 > 1)
    for(uint32_t i = 1; i < s1; i++)
    {
      TWO_PIXELS_RED
    }
  if (s1 > 0)
    {
      TWO_PIXELS_LIGHT_RED
    }
  for(uint32_t i = 0; i < s0; i++)
  {
      TWO_PIXELS_RED
      TWO_PIXELS_RED
      TWO_PIXELS_RED
      TWO_PIXELS_RED
      TWO_PIXELS_RED
      TWO_PIXELS_LIGHT_RED
  }
}

/*void setCurrentADC() {
  TIMER4_init(); // частота дискретизации
  putScale(); // (24);      
}*/

/*void setCurrentFilter() {
  TIMER2_init(); // управление фильтром
  Filter_SetCoeff(0, filterFreqCode[stateF]); // фильтр А, коэффициент частоты 0 (f = f0*71.09)
  Filter_SetCoeff(1, filterFreqCode[stateF]); // фильтр B, коэффициент частоты 0 (f = f0*71.09)
}*/

void printCurrentState() {
  Serial.print("StateA: ");
  Serial.print(stateA);
  Serial.print(", StateF: ");
  Serial.println(stateF);
}

void checkEncoder() { // // проверка состояния энкодера и переключение режимов
  static int32_t oldEncoder = 15;
  int32_t encoder = (TIMER1_BASE->CNT)/2;
  if(encoder != oldEncoder) {
    oldEncoder = encoder;
    int s = stateA;
    stateA = encoder;
    if (stateA < 0) stateA = 0;
    if(stateA > 4) stateA = 4;
    if(stateA != s) {
      TIMER4_init(); // частота дискретизации
      putScale(); // (24);      
#ifdef DEBUG
      printCurrentState();
#endif      
    }
    s = stateF;
    stateF = stateA;
    if(stateF != s) {
      TIMER2_init(); // управление фильтром
      Filter_SetCoeff(0, filterFreqCode[stateF]); // фильтр А, коэффициент частоты 0 (f = f0*71.09)
      Filter_SetCoeff(1, filterFreqCode[stateF]); // фильтр B, коэффициент частоты 0 (f = f0*71.09)
#ifdef DEBUG
      printCurrentState();
#endif      
    }
  }
}

inline void checkSerialInput() { // для отладки - консольное управление частотой дискретизации и частотой фильтра независимо
  if(Serial.available()){
    char ch = Serial.read();
    if (ch >= '0' && ch <= '4') {
      stateA = ch - '0';
      TIMER4_init(); // частота дискретизации
      putScale(); // (24);      
    }
    if (ch >= 'A' && ch <= 'E') {
      stateF = ch - 'A';
      TIMER2_init(); // управление фильтром
      Filter_SetCoeff(0, filterFreqCode[stateF]); // фильтр А, коэффициент частоты 0 (f = f0*71.09)
      Filter_SetCoeff(1, filterFreqCode[stateF]); // фильтр B, коэффициент частоты 0 (f = f0*71.09)
    }
    printCurrentState();
  }
}

void draw(int &ii, int &iii, int &startX) { // рисование на экране всего обновляемого (кроме шкалы)
  if(stateOsc) {     // если нужна осциллограмма, пытаемся найти точку синхронизации
    int maxDelta = 0;  // значение максимума производной
    int startXmax = 0; // позиция максимума функции
    int currMax = 0;   // значение максимума функции
    for(int i = 0; i < 1024-480-3; i++) {
      if(maxDelta < (buffFFT[i+3+240] - buffFFT[i+240])) { // ищем максимум производной
        maxDelta = (buffFFT[i+3+240] - buffFFT[i+240]);
        startX = i;
      }
      if(currMax < buffFFT[i+240]) {                     // ищем максимум функции
        currMax = buffFFT[i+240];
        startXmax = i;
      }
    }
    static bool searchMax = false; // признак - искать по максимуму функции (true) или максимуму производной (false)
    if(searchMax && (maxDelta > 350)) searchMax = false; //
    if(!searchMax && (maxDelta < 250)) searchMax = true; // переключаем режимы с гистерезисом
    if(searchMax) startX = startXmax;
  }
  int prev = -1;  // значение в предыдущей точке, нужно только в режиме 2 (откуда рисовать отрезок прямой)
  for(int i = 1; i < 481; i++){
    int32_t s = 1002 - getLog6(x[i]*x[i] + x[1024-i]*x[1024-i] + y[i]*y[i] + y[1024-i]*y[1024-i]);
    if (ii < s) ii = s;      // s - логарифм в единицах, отмасштабированных к экрану
    if (iii > s) iii = s;
    if(s < 0) s = 0;
    if(s > 150) s = 150;
    H_line_b(0, i-1, s);        // рисуем верхнюю и нижнюю части
    H_line_r(s, i-1, 150-s);    // вертикального столбца соответствующими цветами
    if(stateOsc) {
      uint32_t xx = 150 + (buffFFT[i + startX] - 2048)/14; // xx - координата, соответствующая отсчету
      if((stateOsc == 1) || (xx == prev) || (prev == -1)){   // точечная осциллограмма (или горизонтальный фрагмент линейной)
        Address_set(xx, i-1, xx, i-1);
        Lcd_Write_Pixel(0xffff);
      } else {                        // осциллограмма линиями
        if (xx <= prev) {
          Address_set(xx, i-1, prev, i-1);
          for (int j = xx; j < prev; j++) { Lcd_Write_Pixel(0xfffe0); }
        } else {
          Address_set(prev, i-1, xx, i-1);
          for (int j = prev; j < xx; j++) { Lcd_Write_Pixel(0xfffe0); }
        }
      }
      prev = xx;
    }
  }
}

void setup()
{
#ifdef DEBUG
  Serial.begin(9600);
  while (!Serial) { ; } // wait for serial port to connect.
#endif
  GPIOB_BASE->ODR = CTRL_MASK_DEFAULT;  // все управляющие HIGH
  GPIOB_BASE->CRL = 0x11111411;         // пины PB7-PB3, PB1-PB0 - выход, PB2 - вход (не исп.)
  GPIOB_BASE->CRH = 0x44411111;         // пины PB12-PB8 - выход, PB15-PB13 - не исп.
  GPIOB_BASE->ODR = CTRL_MASK_DEFAULT;  // 
  GPIOA_BASE->CRL = 0x111111b0;         // пины PA7-PA2 - выходы упр.фильтром, PA0 - аналоговый вход, PA1 - выход таймера (для фильтра)
  GPIOA_BASE->CRH = 0x14444444;         // пин PA15 - выход упр.фильтром (WR), PA8-PA10 - вход энкодера, PA11-PA14 - не исп.

  Lcd_Init();
  buffADC = ADCBuffer;
  buffFFT = ADCBuffer2;
  DMA_init();
  ADC_init();
  TIMER4_init(); // частота дискретизации
  TIMER2_init(); // управление фильтром
  TIMER1_init(); // энкодер
  
  for(int i = 0; i < size; i++) {
    buffADC[i] = 0;
    buffFFT[i] = 0;
  }
  putScale(); // (24);
  Filter_SetMode(0, 1); // фильтр А, режим 2
  Filter_SetMode(1, 1); // фильтр В, режим 2
  Filter_SetCoeff(0, filterFreqCode[stateF]); // фильтр А, коэффициент частоты 0 (f = f0*71.09)
  Filter_SetCoeff(1, filterFreqCode[stateF]); // фильтр B, коэффициент частоты 0 (f = f0*71.09)
  Filter_SetQ(0, 59); // фильтр А, коэффициент добротности 59 (Q=1.31)
  Filter_SetQ(1, 1); // фильтр B, коэффициент добротности 59 (Q=1.31)
}

void loop()
{
//  while((DMA1_BASE->ISR & 2)== 0) { ; }
  int t0 = millis(); 
  ADC_init();
  resetDMA();
  TIMER4_BASE->CNT = 0;
  for(int i = 0; i < size; i++) {
    x[i] = (buffFFT[i] - 2048)*w[i];
    y[i] = 0;
  }
  
  FFT(); 

  bool static buttonPressed = HIGH;
  {
    bool button = digitalRead(PA10);
    if(!button && buttonPressed) stateOsc++;
    if(stateOsc > 2) stateOsc = 0;
    buttonPressed = button;
  }
  int ii = 0;
  int iii = 1320;
  int startX = 0;

  while((DMA1_BASE->ISR & 2)== 0) { ; }
  draw(ii, iii, startX);

#ifdef DEBUG
  int t1 = millis();

  static int nn = 0;
  if(!(nn%200)) {
    Serial.print(" Y: ");
    Serial.print(screenX); // Y+     PB13 connect to PB0 (RS - SDA)
    Serial.print(' ');
    Serial.print(" X: ");
    Serial.print(screenY); // X-     PB12 connect to PB1 (CS - SCL)
    Serial.print(" dt: ");
    Serial.print(t1-t0); 
    Serial.print(" min "); 
    Serial.print(ii); 
    Serial.print(" max "); 
    Serial.print(iii); 
    Serial.print(" start "); 
    Serial.println(startX); 
  }
    nn++;
  if(nn > 10)
  checkSerialInput();
#endif
  checkEncoder();
}

Файлы работы с фильтром, заголовочный MAX261.h:

#ifndef _MAX261_h_
#define _MAX261_h_
#include <Arduino.h>

#define NOP __asm__ __volatile__ ("nop\n\t")
#define nop __asm__ __volatile__ ("nop\n\t")
#define Nop __asm__ __volatile__ ("nop\n\t") 

// фильтры адресуются по номеру% 0 - А, 1 - В
void Filter_SetMode(byte channel, byte mode); // 1-4 - установка режима (используются только 1 и 2 (коды 00 и 01 соответственно))

void Filter_SetQ(byte channel, byte cQ); // установка добротности фильтра, 7 разрядов

void Filter_SetCoeff(byte channel, byte cF); // установка доп. коэффициента частоты (от 100 до 200) - 6 разрядов от 0 до 63

#endif

и MAX261.cpp:

#include "MAX261.h"

//#define DEBUG_MAX261
#define WR_FILTER (0x8000)

// WR - PA15
// A3 - PA7
// A2 - PA6
// A1 - PA5
// A0 - PA4
// D1 - PA3
// D0 - PA2

void checkFilter(uint16_t mask);

#define WRITE_TO_FILTER                                                             \
  GPIOA_BASE->ODR = mask; /* нужна пауза 25 мкс - меньше 2 тактов */                \
  GPIOA_BASE->BRR = WR_FILTER;                                                      \
  NOP; NOP; NOP; NOP; NOP; NOP; NOP; /* 7 тактов (всего нужно 250 нс - 9 тактов) */ \
  GPIOA_BASE->BSRR = WR_FILTER; /* нужна пауза 10 мкс - менее 1 такта */

void Filter_SetMode(byte channel, byte mode) { // 1-4  - установка режима работы (используются только 1 и 2 (коды 00 и 01 соответственно))
#ifdef DEBUG_MAX261
  Serial.print("FilterSetMode: ");
  Serial.print(channel);
  Serial.print(", mode: ");
  Serial.println(mode);
#endif  
// для режима адрес 0, а старший бит у фильтра B - единица.
  uint16_t mask = WR_FILTER + ((channel & 1) << 7) + ((mode << 2) & 0x0c);
  WRITE_TO_FILTER
#ifdef DEBUG_MAX261
  checkFilter(mask);
#endif  
}

void Filter_SetQ(byte channel, byte cQ) { // установка добротности фильтра, 7 разрядов, WR,-,-,-,-,-,-,-,A3,A2,A1,A0,D1,D0,-,-
// добротность имеет 7 разрядов и пишется по адресам  с 4 по 7, старший бит адреса для порта B равен 1
#ifdef DEBUG_MAX261
  Serial.print("FilterSetQ: ");
  Serial.print(channel);
  Serial.print(", Q: ");
  Serial.println(cQ);
#endif  
  uint16_t mask = WR_FILTER + ((channel &1) << 7) + ((cQ << 2) & 0x0c) + 0x40;
  WRITE_TO_FILTER
#ifdef DEBUG_MAX261
  checkFilter(mask);
#endif  
  
  mask = WR_FILTER + ((channel &1) << 7) + ((cQ     ) & 0x0c) + 0x50;
  WRITE_TO_FILTER
#ifdef DEBUG_MAX261
  checkFilter(mask);
#endif  
  
  mask = WR_FILTER + ((channel &1) << 7) + ((cQ >> 2) & 0x0c) + 0x60;
  WRITE_TO_FILTER
#ifdef DEBUG_MAX261
  checkFilter(mask);
#endif  
  
  mask = WR_FILTER + ((channel &1) << 7) + ((cQ >> 4) & 0x0c) + 0x70;
  WRITE_TO_FILTER
#ifdef DEBUG_MAX261
  checkFilter(mask);
#endif  
}

void Filter_SetCoeff(byte channel, byte cF) { // установка доп. коэффициента частоты (от 100 до 200) - 6 разрядов от 1 до 63
#ifdef DEBUG_MAX261
  Serial.print("FilterSetCoeff: ");
  Serial.print(channel);
  Serial.print(", fr: ");
  Serial.println(cF);
#endif  
  uint16_t mask = WR_FILTER + ((channel &1) << 7) + ((cF << 2) & 0x0c) + 0x10;
  WRITE_TO_FILTER
#ifdef DEBUG_MAX261
  checkFilter(mask);
#endif  
  
  mask = WR_FILTER + ((channel &1) << 7) + ((cF     ) & 0x0c) + 0x20;
  WRITE_TO_FILTER
#ifdef DEBUG_MAX261
  checkFilter(mask);
#endif  
  
  mask = WR_FILTER + ((channel &1) << 7) + ((cF >> 2) & 0x0c) + 0x30;
  WRITE_TO_FILTER
#ifdef DEBUG_MAX261
  checkFilter(mask);
#endif  
}

void checkFilter(uint16_t mask) {
  static uint16_t currStateA = 0;
  static uint16_t currStateB = 0;
  uint16_t addr = (mask & 0x00f0) >> 4;
  uint16_t data = (mask & 0x000c) >> 2;
  if(addr & 0x0008) { // channel B
    currStateB &= ~(0x0003 << ((addr & 0x0007)*2));
    currStateB |= data << ((addr & 0x0007)*2);
    Serial.print("channel B ");
    Serial.print(currStateB, HEX);
    Serial.print(", mode : ");
    Serial.print(currStateB & 3);
    Serial.print(", freq: ");
    Serial.print(36000.0/(TIMER2_BASE->ARR+1)/((((currStateB >> 2) & 0x003f) + 64)*1.11072));
    Serial.print(", Q: ");
    Serial.print((currStateB >> 8) & 0x007f);
    Serial.print(", mask: ");
    Serial.println(mask, HEX);
  } else {            // channel A
    currStateA &= ~(0x0003 << ((addr & 0x0007)*2));
    currStateA |= data << ((addr & 0x0007)*2);
    Serial.print("channel A ");
    Serial.print(currStateA, HEX);
    Serial.print(", mode : ");
    Serial.print(currStateA & 3);
    Serial.print(", freq: ");
    Serial.print(36000.0/(TIMER2_BASE->ARR+1)/((((currStateA >> 2) & 0x003f) + 64)*1.11072));
    Serial.print(", Q: ");
    Serial.print((currStateA >> 8) & 0x007f);
    Serial.print(", mask: ");
    Serial.println(mask, HEX);
  }
}

Управление:

- энкодер переключает диапазоны: 5 кГц, 10 кГц, 20 кГц, 40 кГц или 60 кГц,

- кнопка энкодера по кругу переключает: только спектр, спектр + точечная осциллограмма, спектр + осциллограмма линиями,

- тумблер на 3 положения: +0 дБ, +15дБ, +30 дБ,

- плавный регулятор уровня.