Конструкция выходного дня: простой спектранализатор 480 полос 60 дБ на stm32f103 и ILI9481
- Войдите на сайт для отправки комментариев
Собственно возникла идея собрать спектранализатор звуковой частоты на популярном дисплейчике 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%.
Часть этих искажений дает генератор, а часть - АЦП контроллера. По крайней мере, при уменьшении сигнала на десятки процентов вторая гармоника уменьшается на несколько дБ, но не исчезает совсем.
В общем, прибор оказался довольно чувствительным.
Да, вдогонку: как видно на фото, сигнал на вход подается амплитудой 1.6 В со смещением 1.65 В напрямую.
В случае работы с реальным сигналом, например, с аудиовыхода, естественно, необходимы дополнительные входные цепи.
В простейшем случае - это делитель из двух резисторов по 100 кОм между землей и питанием, середина которого соединена со входом контроллера. А сигнал подается в эту же точку через конденсатор 0.22 мкФ. На всякий случай последовательно с конденсатором можно включить резистор на несколько кОм.
Ну а по хорошему - на входе ФНЧ на ОУ и защита на стабилитроне.
Кстати, фильтр очень желателен: из-за высокого динамического диапазона прекрасно видны все зеркальные частоты от сигнала, спектр которого не ограничен половиной частоты дискретизации. Например, банального меандра.
А с чем связна такая форма обращения к регистрам:
ppp_BASE->rrr
? Обычно все ж пишут DMA1_Channel1->CCR, а не DMA1_BASE->CCR.Ну эту форму не я придумал. Почему-то и GPIO и TIMERn - все задаются через *_BASE. И, кстати, в комплекте Arduino IDE есть файлик dma.h, в котором DMA подключается именно таким образом.
Я понял -- это старый вариант STM32DUINO. Возможно, имеет смысл задуматься о переходе на новый.
Я нашёл на форуме упоминание Роджера Кларка (автора основной библиотеки и основателя/модератора Stm32duino.com) про существование других вариантов аддонов под ардуино: https://www.stm32duino.com/viewtopic.php?t=97 В общем он пишет, что этот вариант от самих STM, правда на st.com об этом репозитории никаких упоминаний не нашёл, т.к. что возможно что это пока делается полуофициально. На форуме stm32duino есть отдельный раздел посвящённый этому ядру http://stm32duino.com/viewforum.php?f=48
Вопрос переходить или нет на новое ядро от stm пока остаётся открытым... :)
Честно говоря, я не любитель менять что-то на более новое, пока это "что-то" работает.
Это - первое.
Второе:
Когда искал примеры/аналоги работы с DMA, обнаружил, что гораздо больше исходников, ориентированных на keil (что, впрочем, логично). Но это немного более высокий уровень. Думаю, что с keil рано или поздно познакомиться придется, но для начала лучше разобраться на низком уровне - через регистры - что как раз и дает Ардуино. А, судя по записям в "новом" варианте, там уже обращение идет не к регистрам, а к битовым полям внутри регистра.
Нет никакой отдельной сущности регистров в виде битовых полей. Разряды регистра и биты этих самыых "полей" -- суть одно и то же. То, что у Кларка может быть записано так:
на CMSIS будет выглядеть так:
Никаких волшебных цифр, самодокументируемый, по сути, код. Без проблем можно таскать куски кода между ардуиной и другими средами (кейл, иар и пр.).
Нет смысла набивать руку на кларковских изобретениях, т.к. это дополнительно ничего не дает, но кроме конкретно этой реализации более неприменимо нигде.
Всё таки из принципа покопался, и нашёл на st.com ссылку на ардуиновский аддон, она была в мануале к одной из отладочных плат. Поставил аддон, из того что бросилось в глаза -нет загрузки с бутлоадера. :(
А разве "Serial" -- это не оно?
a5021, ну это аппаратный сериал с выходом пинов на плату, то есть требует отдельного переходника. А в родном аддоне был ещё полусофтовый сериал (USB-CDC), который позволял через usb-разъём прошиваться.
Я с такими тонкостями дел не имел, т.к. привык для плат без лоадера просто внешний программатор цеплять. С другой стороны, наверняка есть возможность использовать этот USB-CDC и с Arduino_Core_STM32. Те же NUCLEO-64 имеют возможность заливки скетчей вообще через MassStorage, а одна из этих плат, как раз на F103.
Ну, раз уж разговор ушел в сторону: никто не имел дела с stm32f407?
Вчера пришла плата. В документации, вроде, написано, что загрузчик для нее в отличие от f103 не нужен, т.к. есть изначально. Но что-то моя Винда говорит - неопознанное устройство. Я так понимаю, нужен драйвер, но драйвер от f103 не подходит, а поиск по и-нету пока результатов не дал.
Что хоть за плата?
Плата такая: https://ru.aliexpress.com/item/1-STM32F407VET6-Cortex-M4-STM32/32864003267.html
Может оказаться, что только дядюшке Ляо известно о прошивке внутри и том, какие драйвера под это дело потребны.
andriano, мапловский может не подходить из-за того, что виды-пиды там нестандартные вписаны. Может попробовать родной драйвер?
Драйвер здесь
Плата такая: https://ru.aliexpress.com/item/1-STM32F407VET6-Cortex-M4-STM32/32864003267.html
ну и моща... почти 200 МГц, полмегабайта памяти, почти сотня GPIO, сеть и прочие интерфейсы на борту.
Куда только все это использовать в таком формате...
Andriano - если есть задумки, подо что брали плату - поделитесь, интересно.
1. Увы, ни одна из ссылок не помогла.
2. Просто заинтересовался stm32, захотелось попробовать что-нибудь посерьезнее f103. Тут, вроде, и интерфейс для камеры и FPU, так что для чего-нибудь, думаю, пригодится.
И китаец не отвечает, похоже, все ушли в запой - у них сейчас главный государственный праздник. Если до понедельника не прочухается, буду открывать спор.
Может, ее не через USB, а через JTAC/SWD надо подключать?
Может, ее не через USB, а через JTAC/SWD надо подключать?
а может не тот USB - в описании говорится, что на плате аж три распаянных разъема. Да еще и посмотрите, как оно распаяно - помните - на F103 надо было резистор напаять, чтобы USB опознался.
Наверное пора переходить с 4-е измерение, потому как в этих я двух других USB разъемомв не вижу
У меня на двух компах вполне распознается без дополнительных резисторов все 4 имеющиеся платы при том, что они явно относятся к двум слегка отличающимся семействам. Не распознается лишь одна - как раз та, на которую резисто был напаян (в попытках ее запустить, но реанимировать ее не удалось). Кстати, на этой плаие, если верить схеме, на USB стоит как раз 1.5 кОм.
Может, ее не через USB, а через JTAC/SWD надо подключать?
А вы прям где-то прочли, что с заводской прошивкой можно через USB-CDC можно прошиваться?
Где-то прочел: 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.
Может я это как-то неправильно понял?
Boot select правильно выставил?
Вот сейчас спросил у Гугла: "boot select - что это такое". Но, увы, среди предложенных им вариантов ничего толкового не обнаружил.
Чтобы было понятно: с "голым" кристаллом никогда в жизни не работал и не собираюсь, во фьюзах AVR никогда не пытался разбираться и даже не знаю, существует ли что-нибудь аналогичное у stm32. SMD никогда не паял и не собираюсь, т.к. DIP 2.54 паяю в двух разных очках, причем эти очки не совпадают с теми, в которых читаю. А потому раз и навсегда решил для себя, что имею дело только с готовыми платами либо DIP микросхемами и не-SMD рассыпухой.
Да, если boot select - это те два джампера на плате, которыми каким-то образом (не разбирался, каким) определяется порядок загрузки, то они у меня всегда на всех платах стоят в положении по умолчанию - 00. Насколько мне известно, и Arduino и ST-Link работают с платой именно в этом режиме.
PS. Сейчас буду пробовать подключить через ST-Link.
Загрузчики находятся в System memory
Насколько я понял, этот тот режим, в котором нужно прошивать через Serial.
Но я попытался сделать по другому: сначала, естественно, на "кошечках", т.е. прошил блинк в f103 через ST-Link. А когда убедился, что это работает, выяснил, что светодиоды на f407 подключены на PA6 и PA7, подправил блинк под f407 и прошил его. Судя по поведению светодиодов - успешно.
Теперь вопрос - а отлаживаться-то как? Без текстовой консоли - неудобно.
PS. Еще непонятка - почему-то Arduino IDE сообщает только о 128К оперативки, хотя, вроде, должно быть 196К.
В принципе давно собирался попробовать Keil, но все оказии не подворачивалось. Ну и немного смущает использование инструментального средства с ограничеием 32к для камня, у которого 512к. И где-то здесь читал, что Keil требует обновления прошивки ST-Link.
Ладно, возвращаюсь к топику.
Как-то на рабочей неделе не получаетя выкроить время для проекта, поэтому действительно получается "проект выходного дня", но не на одну неделю.
Сейчас нахожусь в размышлении, как лучше сделать управление режимами работы. Хочется иметь возможность переключать диапазон (т.е. по сути - частоту дискретизации) и помещать (имли убирать) поверх спектра еще осциллограмму и частоту, если будет обнаружено, что сигнал периодический.
Хотел вообще отказаться от отдельных органов управления, но оказалось, что имеющийся у меня экран 320х480 не сенсорный. И, похоже, другие экраны с таким же разрешением - такие же.
Значит - энкодер. Проблема лишь в том, достаточно ли его будет или потребуются дополнительные кнопки.
Ну и подключение самого энкодера: stm32 позволяет подключить энкодер без использования ЦПУ, - используя возможноси аппаратного таймера. Но такое решение ограничивает в выборе пинов. Пока не определился, что лучше: такое аппаратное решение или стандартное решение на прерываниях.
Может я это как-то неправильно понял?
1. Переключил джампер на загрузку из системной памяти, в диспетчере устройств появился stm32 BOOTLOADER, и как им теперь воспользоваться?
На попытку прошивки через Arduino IDE отвечает:
Честно говоря, по этой диагностике я понял, что с ком-портом - дело труба, а вот с DFU - то-ли нашел (в строках 13-16), то-ли не может найти (строка 10) - какая-то противоречивая информация.
По факту - светодиоды должным образом не заработали (точнее, вообще не заработали).
Что делать дальше, как им пользоваться?
Что делать дальше, как им пользоваться?
Без ручных манипуляций не обойтись, в этом смысле такой вид загрузки не очень удобен.
Тогда непонятна фраза "В 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?
Так в чем же разница? Почему считается, что у одного есть загрузчик, а у другого - нет?
Проект постепенно продвигается: нарисовал и распечатал корпус и конструктивные элементы, соделал экран "полосатым" - через 3 дБ теперь неяркие горизонтальные полоски. Заодно за счет разворачивания циклов немного подросла скорость - тепеь примерно 11 fps.
При помощи энкодера сделал переключение между 5-ю диапазонами измерения: 5 кГц, 10 кГц, 20 кГц, 40 кГц и 60 кГц. Частотный диапазон ограничен сверху фильтром на микросхеме MAX261, но об этом позже.
Краем уха услышал, что таймеры в stm32 можно использовать для аппаратной поддержки энкодера. В принципе, на прерываниях привычнее, но надо же осваивать новые технологии! Тем более, что свободные таймеры есть: один используется для задания частоты дискретизации звука, другой - для управления фильтром, третий - для энкодера и еще один остается в запасе.
Правда, энкодер работает как-то странно: то на один щелчок изменяет счетчик на 1, то на 2. У меня даже сложилось впечатление, что сначала некоторое время - на 1, а потом переключаеся в режим - на 2. Чтобы не было пропусков, делю значение счетчика на 2 - тогда сначала происходит переключение не на каждый щелчок. Но все лучше, чем если бы перескакивал.
Выяснилось, что высокочастотный сигнал управления фильтром (до 4 МГц) проникает в звуковой тракт и прекрасно виден на экране спектранализатора. Естественно, не сами 4 МГц, а разностные частоты. А разностная частота в данном случае была 4000 - 48*83 = 16 кГц. Вот она и была видна, пока частота дискретизации составляла 48 кГц. Пришлось подкорректировать частоту дискретизации так, чтобы она делила 4 МГц нацело.
Код:
Все это собрано на макетке для Меги. Да, первоначальные варианты отлаживал на уже спаянной макетке для проекта с видеокамерой, теперь на такой же макетке собрал новую схему:
Перекинул управляющие сигналы на другие пины. В том числе и чтобы подключить энкодер: если для прерываний можно использовать любые пины, то для таймера выбора практически нет.
Незанятая панелька в левой части платы - для фильтра. Для фильтра также нужно двухполярное питание, поэтому в самой левой части платы стоит преобразователь B0505, с которого я беру -5В. Справа снизу - стабилизатор. В какой-то фабричной MIDI-плате увидел, как сделано питание:
с разъема питание - сразу на мост (чтобы переполюсовка не приводила к печальным последствиям - обычный штыревой разъем питания ведь не стандартизован: "+" может быть как на центральном, так и на внешнем контакте).
после моста - стабилизатор на 5 В.
а уже после стабилизатора на 5 В там бал преобразователь на плюс-минус 12 В - для питания операционников.
В общем, что-то подобное решил сделать здесь.
"Кроватка" фильтра пока пустая. Вставил тонкую проволочку между входом и выходом фильтра - пока отлаживаю без него.
andriano, ну если что у меня пример обработчика энкодера есть в генераторах 3.x, работает отлично, настроен так, что-б на каждый щелчок выпадать в прерывание.
dimax, во-первых, это stm32, ф не avr, а во-вторых, - энкодер работает без прерываний - аппаратно на таймере. Есть у него такой режим работы.
На avr и прерываниях у меня тоже стабильно 80 единиц за оборот (соответственно, можно 40 либо 20), там проблем нет.
andriano, вы не посмотрели что я вам написал. Версии 3.x -это на stm32, и обработчик там на таймере, именно таймер у меня организовывает прерывания, это сразу станет понятно посмотрев настройки в сетапе.
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-разрядный АЦП). Т.е. малейшие шумы, включая шумы дискретизации, на индикаторе будут видны.
Пасибки. Вот оказывается почему у меня не получилось оцифровав звук (речь) достоверно выделить форманты и "опознать голос". Хотели с сыном подавать команды роботу голосом, как-то сразу не задалось, а потом бросили. Сначала надо фильтровать, а потом оцифровывать, а не наоборот.. пасибки.
Ну хорошо, хоть кому-то понадобилось. А то, когда писал, очень сильно сомневался, стоит ли излагать хотя бы кусочки теории.
Конечно, и больше чем уверен что не только мне. За долгую жизнь многа раз убеждался что всегда найдется N+1 читатель, которому это точно полезно, ИМХО, не стоит таким вопросом заморачиваться даже на "тютельку". :)
Кстати! Появилась мысля применить "программный фильтр", типа нашего восстановления засветки ИК-датчика. Вы можете попробовать на своей схеме, он прост: берем эталонный сигнал (напр. прямоугольник) и смотрим величину "отражений". Как видно по вашим картинкам - отражения распространяются от максиумов (и вообще от найденных частот) по определенному закону. Вот этот "закон" и вычитаем, из спектра в порядке ранжирования максиумов (и вообще найденных частот).
Т.е. смотрим, что +-1 гребенка меньше максиума на хх%, +-2-я гребенка .. думаю достаточно. Фиксим эти проценты, и получив спектр, сортируем его копию по убыванию амплитуды частот и в порядке копии "нормируем" спектр на эти проценты.
Попробуйте, мне наши микрофонные уши сейчас не проверить. (очень не скоро).
Arhat109-2, алгоритма не понял, но, думаю, Вы и сами в нем не разобрались.
Видите-ли, при "отражении" происходит необратимая потеря информации. Следовательно, никакие попытки восстановления сигнала в принципе не могут привести к успеху.
Ну хорошо, в процессе измерений я пользовался периодическим сигналом с генератора. Там можно хоть пытаться найти какие-то закономерности (реальные или мнимые - это отдельный вопрос) А будет музыкальный сигнал с непрерывным спектром?
Насчет энкодера - выяснилась закономерность: допустим я задаю 5 позиций от 0 до 4. Если я кручу энкодер в пределах первого круга, он выдает 2 (1 - в зависимости от настроек) импульса на щелчок, а как только перехожу через границу (т.е. либо "ниже 0", либо "выше 4"), то он необратимо переходит в режим 4(2) импульса на щелчок. И побороть это мне пока не удалось.
dimax, к сожалению, воспользоваться Вашим кодом я не смог. Подозреваю, что Вы вообще нестандартно используете энкодер - лишь для того, чтобы он пропускал "лишние" импульсы, когда их 2 или 4 на щелчок, а подсчитывает положение энкодера сам камень через прерывания.
Т.е. я хочу вообще избавиться от прерываний, настроить энкодер по счету от 0 до 4 и чтобы положение энкодера я мог узнавать, читая TIMER4_BASE->CNT.
Arhat109-2, алгоритма не понял, но, думаю, Вы и сами в нем не разобрались.
Видите-ли, при "отражении" происходит необратимая потеря информации. Следовательно, никакие попытки восстановления сигнала в принципе не могут привести к успеху.
Ну хорошо, в процессе измерений я пользовался периодическим сигналом с генератора. Там можно хоть пытаться найти какие-то закономерности (реальные или мнимые - это отдельный вопрос) А будет музыкальный сигнал с непрерывным спектром?
dimax, к сожалению, воспользоваться Вашим кодом я не смог. Подозреваю, что Вы вообще нестандартно используете энкодер - лишь для того, чтобы он пропускал "лишние" импульсы, когда их 2 или 4 на щелчок, а подсчитывает положение энкодера сам камень через прерывания.Т.е. я хочу вообще избавиться от прерываний, настроить энкодер по счету от 0 до 4 и чтобы положение энкодера я мог узнавать, читая TIMER4_BASE->CNT.
А в чём проблема? Сделать ARR=65535.
А падать в прерывание по достижении %ARR% или нет -это дело хозяйское :)
dimax, мне нужно не 65535, мне нужно 4 ровно! Иначе аппаратный энкодер не будет выполнять своих функций. Можно, конечно программно вычислят остаток по модулю 5, и даже при движении "вверх" у нас не будет наступато "переполнение" и связанное с ним изменение коэффициента пересчета. Но ведь при движении "вниз" по пересечению 0 у нас этот нежелательный эффект останется.
Так что вопрос не решен.
Да, в процессе экспериментов выяснил, что в самом низкочастотном дианазоне уровень шумов примерно на 10 дБ выше, чем в остальных:
Оказалось, время, за которое вводится массив отсчетов, больше, чем время FFT, и скетч начинал передавать данные на экран, когда еще шла оцифровка сигнала. Отсюда и помеха, имеющая вид широкополосного шума.
Написал - не начинать работу с экраном, пока не закончился ввод данных. Помогло.
andriano , Мы явно не понимаем друг друга. Что вам надо то? То вы пишите "чтобы положение энкодера я мог узнавать, читая TIMER4_BASE->CNT" Я дал скетч, узнавайте через CNT. С нолём проблем нет, ставьте начало счёта на 32767, и пожалуйста, по 30 тыщ щелчков счёта в любую сторону. Читаете счётный регистр в лупе или где надо, смотрите в какую сторону крутилось, счётчик опять скидываете на 32767. Совершенно не понимаю в чём тут может быть проблема.
dimax, хотелось полностью аппаратное решение, а Вы опять предлагаете программную обработку.
Ладно, решил пока проект на этом завершить.
Пока фотографировал, понял, почему приборы делают по 20 см в глубину, хотя корпус совершенно пустой. Я сделал 5 см и уже два раза только за время фотографирования он у меня упал. В общем, первоначально думал нарисовать и распечатать заднюю стенку на 3D-ринтере, но сейчас появилось желание сделать ее из толстого металла, миллиметров так 5, чтобы неглубоктй корпус не опрокидывался вперед при малейшем воздействии, например, перемещении сигнального кабеля.
Фото с генератора:
Фото с звуковым сигналом (MP3-плеер):
Схема:
Скетч:
Файлы работы с фильтром, заголовочный MAX261.h:
и MAX261.cpp:
Управление:
- энкодер переключает диапазоны: 5 кГц, 10 кГц, 20 кГц, 40 кГц или 60 кГц,
- кнопка энкодера по кругу переключает: только спектр, спектр + точечная осциллограмма, спектр + осциллограмма линиями,
- тумблер на 3 положения: +0 дБ, +15дБ, +30 дБ,
- плавный регулятор уровня.