Medindo a harmonia - analisador de espectro de som no STM32L4 Discovery

Em um post anterior , conectamos uma tela LCD chinesa barata à placa STM32L4 Discovery . Agora, tentaremos implementar nessa combinação algo que vai além do piscar tradicional de um LED, a saber, um analisador de espectro de som que usa o microfone na placa. Ao mesmo tempo, mostrarei como usar o sistema operacional FreeRTOS, e por que é necessário, e por que existem 12 notas em uma oitava musical e mais de 53 notas são melhores que 12.





Digitalização de som


Queremos receber o sinal do microfone, calcular seu espectro usando a transformada rápida de Fourier (FPU para nos ajudar) e mostrar o resultado no LCD na forma de uma 'cascata colorida'. A força do som será codificada em cores. Desenharemos uma linha de pixels a partir da borda da tela, onde o pixel mais à esquerda corresponderá à frequência mínima e o mais à direita corresponderá à máxima, enquanto a imagem anterior será deslocada em uma linha, liberando espaço para uma nova linha. Nosso microcontrolador é muito complicado para começar do zero, então vamos começar com um exemplo do kit STM32Cube chamado DFSDM_AudioRecord. O que é DFSDM? Este é o filtro digital para modulação Sigma-Delta. O fato é que, diferentemente dos bons e antigos microfones analógicos, o da placa Discovery não emite um sinal na forma de uma tensão proporcional à pressão do som,e como uma sequência de zeros e outros com uma frequência de relógio de vários megahertz. Se você passar essa sequência por um filtro passa-baixo, obtém o mesmo sinal analógico. Nos modelos anteriores de microcontroladores, era necessário fazer um filtro digital para receber um sinal de áudio em formato digital. Agora o microcontrolador possui um módulo especial para isso, e tudo o que é necessário é configurá-lo no início do programa. Para fazer isso, você pode ler a documentação ou usar um exemplo pronto. Eu fui pelo segundo caminho. A figura a seguir ilustra a estrutura interna do programa DFSDM_AudioRecord.Nos modelos anteriores de microcontroladores, era necessário fazer um filtro digital para receber um sinal de áudio em formato digital. Agora o microcontrolador possui um módulo especial para isso, e tudo o que é necessário é configurá-lo no início do programa. Para fazer isso, você pode ler a documentação ou usar um exemplo pronto. Eu fui pelo segundo caminho. A figura a seguir ilustra a estrutura interna do programa DFSDM_AudioRecord.Nos modelos anteriores de microcontroladores, era necessário fazer um filtro digital para receber um sinal de áudio em formato digital. Agora o microcontrolador possui um módulo especial para isso, e tudo o que é necessário é configurá-lo no início do programa. Para fazer isso, você pode ler a documentação ou usar um exemplo pronto. Eu fui pelo segundo caminho. A figura a seguir ilustra a estrutura interna do programa DFSDM_AudioRecord.



O som digitalizado usando DMA cai no buffer de toque. O DMA causa uma interrupção duas vezes: uma vez - quando o buffer estiver meio cheio, a segunda vez - quando estiver cheio. A rotina de interrupção simplesmente define o sinalizador apropriado. A função main () após a inicialização executa um loop infinito no qual esses sinalizadores são verificados e, se o sinalizador estiver definido, a metade correspondente do buffer será copiada. Um exemplo copia dados para outro buffer, de onde eles, novamente usando DMA, são enviados para o amplificador de fone de ouvido. Eu deixei essa funcionalidade, adicionando o cálculo do espectro do sinal de áudio.

Quando há muitas tarefas


A maneira direta de adicionar novas funcionalidades ao nosso código é adicionar mais sinalizadores e escrever funções que serão chamadas se esses sinalizadores estiverem configurados. O resultado geralmente é uma bagunça de sinalizadores, funções de manipulador e o contexto global, que é forçado a ser global, pois a solução de um problema é dividida em várias pequenas etapas implementadas por funções individuais - manipuladores de eventos. Uma maneira alternativa é confiar o gerenciamento de tarefas a um sistema operacional, como o FreeRTOS. Isso permite simplificar significativamente a lógica devido ao fato de que cada tarefa é resolvida dentro de seu próprio ciclo de processamento de eventos que interagem entre si através das funções do sistema operacional. Por exemplo, podemos adicionar uma tarefa de processamento de dados como um ciclo separado,que aguardará a conclusão dos dados no primitivo de sincronização - semáforo. O semáforo é muito simples: você pode passá-lo se a bandeira estiver marcada e a bandeira for omitida automaticamente. No nosso caso, a fonte de dados exibirá um sinalizador ao preparar dados para outra tarefa. De maneira semelhante, você pode criar cadeias arbitrárias a partir de tarefas da fonte de dados e tarefas do consumidor de dados, semelhantes a como isso acontece, por exemplo, no sistema operacional Linux.no sistema operacional Linux.no sistema operacional Linux.



Obviamente, a execução simultânea de tarefas é uma ilusão, especialmente quando o núcleo da computação é apenas um. Nesse caso, podemos dizer que temos um único thread de execução do programa pelo processador. Os semáforos, como outras primitivas de sincronização, desempenham o papel de uma toca mágica de coelho, na qual o fluxo de execução falha em emergir em outra tarefa.

Conectar o FreeRTOS ao seu projeto é bastante simples. Só é necessário substituir o loop infinito, que geralmente encerra a função main () no microcontrolador, com uma chamada para osKernelStart (). Depois disso, o compilador explicará exatamente o que falta para a compilação. Todas as ações que você executou anteriormente no loop precisam ser transferidas para uma tarefa separada e registradas na chamada xTaskCreate. Depois disso, você pode adicionar quantas tarefas quiser. Deve-se ter em mente que, entre as chamadas para xTaskCreate e osKernelStart, é melhor não inserir nenhum código que funcione com o hardware, pois aqui o timer do sistema pode não funcionar corretamente. A chamada para o manipulador de timer do sistema operacional osSystickHandler () deve ser adicionada ao SysTick_Handler () e as duas funções SVC_Handler e PendSV_Handler devem ser removidas do código,pois eles são implementados no código do SO. Ao registrar tarefas, é importante não cometer erros com o tamanho da pilha. Se for muito pequeno, ocorrerá falhas nos locais mais inesperados. A primeira quando a pilha transborda é a própria estrutura que descreve a tarefa. O IAR tem a capacidade de visualizar uma lista de tarefas. Se você vir uma tarefa com um nome alterado, precisará aumentar o tamanho da pilha.


Para calcular o espectro, usamos a transformada rápida de Fourier. A função correspondente já está na biblioteca. Ela recebe um buffer cheio de dados complexos e forma o resultado lá. Assim, na entrada, ela precisa de um buffer, onde o som digitalizado alterna com zeros (parte complexa 0). Na saída, obtemos números complexos para os quais calculamos imediatamente o quadrado do módulo adicionando os quadrados das partes reais e imaginárias. Fazemos isso apenas para metade do buffer, porque o espectro é simétrico. Nós precisaríamos da segunda metade se quiséssemos fazer a transformação inversa, mas para uma simples exibição do espectro não é necessário. Alguns esforços adicionais são necessários para poder calcular o espectro em diferentes faixas espectrais. Para obter o espectro para baixas frequências,Eu acumulo dados para vários ciclos de leitura do buffer, reduzindo efetivamente a frequência de amostragem do som, que é inicialmente 44,1 kHz. O resultado são 6 faixas - 20kHz, 10kHz, 5kHz, 2600Hz, 1300Hz, 650Hz. Para alternar os intervalos, use o joystick e uma tarefa separada. O joystick também desempenha a função de iniciar / parar a 'cascata', além de ajustar a sensibilidade. É mais conveniente mostrar o espectro em unidades logarítmicas (decibéis), já que sua faixa dinâmica é geralmente muito grande e, em uma escala linear, podemos distinguir apenas os componentes mais fortes do espectro. O logaritmo é considerado muito tempo, mesmo na FPU, então substituí o logaritmo real por uma aproximação linear por partes, que é fácil de obter, sabendoO resultado são 6 faixas - 20kHz, 10kHz, 5kHz, 2600Hz, 1300Hz, 650Hz. Para alternar os intervalos, use o joystick e uma tarefa separada. O joystick também desempenha a função de iniciar / parar a 'cascata', além de ajustar a sensibilidade. É mais conveniente mostrar o espectro em unidades logarítmicas (decibéis), já que sua faixa dinâmica é geralmente muito grande e, em uma escala linear, podemos distinguir apenas os componentes mais fortes do espectro. O logaritmo é considerado muito tempo, mesmo na FPU, então substituí o logaritmo real por uma aproximação linear por partes, que é fácil de obter, sabendoO resultado são 6 faixas - 20kHz, 10kHz, 5kHz, 2600Hz, 1300Hz, 650Hz. Para alternar os intervalos, use o joystick e uma tarefa separada. O joystick também desempenha a função de iniciar / parar a 'cascata', além de ajustar a sensibilidade. É mais conveniente mostrar o espectro em unidades logarítmicas (decibéis), já que sua faixa dinâmica é geralmente muito grande e, em uma escala linear, podemos distinguir apenas os componentes mais fortes do espectro. O logaritmo é considerado muito tempo, mesmo na FPU, então substituí o logaritmo real por uma aproximação linear por partes, que é fácil de obter, sabendoÉ mais conveniente mostrar o espectro em unidades logarítmicas (decibéis), porque sua faixa dinâmica é geralmente muito grande e, em uma escala linear, podemos distinguir apenas os componentes mais fortes do espectro. O logaritmo é considerado muito tempo, mesmo na FPU, então substituí o logaritmo real por uma aproximação linear por partes, que é fácil de obter, sabendoÉ mais conveniente mostrar o espectro em unidades logarítmicas (decibéis), porque sua faixa dinâmica é geralmente muito grande e, em uma escala linear, podemos distinguir apenas os componentes mais fortes do espectro. O logaritmo é considerado muito tempo, mesmo na FPU, então substituí o logaritmo real por uma aproximação linear por partes, que é fácil de obter, sabendoformato para representar um número no float32 . O bit mais significativo é um sinal. Os próximos 8 bits são o expoente binário mais 127. Os bits restantes são a parte fracionária da mantissa, apesar do fato de a parte inteira ser 1 (omitimos as nuances dos números desnormalizados por simplicidade). Assim, tendo selecionado o expoente de float32 e capturado os vários bits mais significativos da mantissa, é possível obter uma boa aproximação do logaritmo. Usando a tabela pré-preparada, convertemos o número resultante em um código RGB para exibição no LCD. Acontece uma escala de cores de 90 ou 60 decibéis. O nível de volume correspondente a zero desta escala pode ser ajustado pressionando o joystick para cima e para baixo.

Exibimos uma foto - sobre os benefícios da leitura de fichas técnicas


Agora só precisamos exibir a imagem e reviver nossa 'cachoeira'. A maneira direta de fazer isso é armazenar a imagem da tela inteira em um buffer, atualizá-la e redesenhar sempre que novos dados aparecerem. Essa solução não é apenas extremamente ineficiente, como também não temos memória suficiente para armazenar a imagem inteira. Parece que o próprio LCD possui memória suficiente para isso e deve ser capaz de fazer algo interessante com ele. De fato, o estudo da folha de dadospermissão para detectar um comando de rolagem até agora não utilizado, que permite alterar dinamicamente a maneira como a memória do controlador LCD é exibida na tela. Imagine que a memória é uma fita fechada em um anel que você vê sob o vidro da tela. O comando Endereço inicial de rolagem vertical (0x37) permite definir a posição na faixa de opções correspondente à borda superior da tela. Então, tudo o que precisamos para reviver a "cascata" é gravar um novo espectro nessa posição e rolar pela fita de memória. O código correspondente foi adicionado ao driver do LCD, emprestado do respeitável Peter Drescher e adaptado conforme descrito aqui. A única desvantagem dessa abordagem: a rolagem funciona apenas no lado mais longo da tela. Por conseguinte, apenas o lado mais curto está disponível para saída de espectro.

12 ?


Vamos seguir para as aplicações práticas do nosso dispositivo. A primeira coisa que é fácil ver no espectro são harmônicos, ou seja, frequências que são múltiplos da frequência fundamental. Especialmente muitos deles na voz. Há também nos sons que fazem instrumentos musicais. É fácil entender por que as notas das oitavas vizinhas diferem em frequência em 2 vezes: então as notas de uma oitava mais alta coincidem em frequência com o segundo harmônico das notas de uma oitava baixa. Dizem que ao mesmo tempo soam "em uníssono". É um pouco mais difícil entender por que existem 12 notas em uma oitava - sete principais (teclas brancas no teclado do piano) e cinco adicionais (teclas pretas). As notas adicionais são indicadas pelas notas principais com caracteres nítidos e planos, embora, de fato, não haja diferença entre elas e as notas principais - todas as 12 notas formam uma progressão geométrica.que a razão de frequências entre notas adjacentes é igual à raiz do 12º grau de 2. O significado dessa divisão da oitava em notas é que, para qualquer nota, existem outras notas que diferem na frequência uma vez e meia - essa combinação é chamada de quinta. As notas que compõem a quinta nota soam em uníssono porque o segundo harmônico de uma nota coincide em frequência com o terceiro harmônico da outra nota. A foto abaixo mostra os espectros das notas Do e Sol, formando um quinto, harmônicos correspondentes são circulados em amarelo.formando um quinto, os harmônicos correspondentes são circulados em amarelo.formando um quinto, os harmônicos correspondentes são circulados em amarelo.



Como as notas 12? Como as notas formam uma progressão geométrica, passamos para os logaritmos. ln (1,5) / ln (2) = 0,58496 ... Um valor aproximado é obtido para a fração 7/12 = 0,583 ... Ou seja, sete meios-tons (intervalos entre as notas adjacentes) são muito próximos de um quint - 1,498. Curiosamente, a fração 31/53 = 0,58491 .. fornece uma precisão muito maior, de modo que o quinto é diferente de 1,5 apenas na quinta casa decimal. Esse fato não passou despercebido, mas os instrumentos musicais com 53 notas em uma oitava não receberam distribuição. Eles são difíceis de afinar, difíceis de tocar e a porcentagem de pessoas que podem sentir a diferença com as ferramentas convencionais é muito pequena.

Código fonte


Está aqui . Para compilação, foi utilizado o IAR Embedded Workbench for ARM 7.50.2. Nenhuma outra biblioteca é necessária para a compilação.

Source: https://habr.com/ru/post/pt391311/


All Articles