Olá amigos!Nos artigos anteriores,
falei sobre o meu projeto e
sua parte do software . Neste artigo, mostrarei como criar um gerador de sinal simples para 4 canais - dois canais analógicos e dois canais PWM.

Canais analógicos
O microcontrolador
STM32F415RG incorpora um conversor
DAC de 12 bits (digital para analógico) em dois canais independentes, o que permite gerar sinais diferentes. Você pode carregar dados diretamente nos registros do conversor, mas isso não é muito adequado para gerar sinais. A melhor solução é usar uma matriz na qual gerar uma onda do sinal e executar o DAC com um gatilho do timer e do DMA. Alterando a frequência do temporizador, você pode alterar a frequência do sinal gerado.
As formas de onda "
clássicas " incluem: sinusoidal, sinuoso, triangular e dente de serra.

A função de gerar essas ondas no buffer é a seguinte Na função, você precisa passar um ponteiro para o início da matriz, o tamanho da matriz, o valor máximo e a forma de onda desejada. Após a chamada, o array será preenchido com amostras de uma onda da forma desejada e você poderá iniciar o timer para carregar periodicamente o novo valor no DAC.
O DAC neste microcontrolador tem uma limitação: o tempo típico de acomodação (o
tempo de carregamento de um novo valor no DAC e quando ele aparece na saída ) é de 3 ms. Mas nem tudo é tão simples - esse tempo é máximo, ou seja, mude de mínimo para máximo e vice-versa. Ao tentar retirar o meandro, essas frentes desarrumadas são muito claramente visíveis:

Se uma onda sinusoidal for emitida, a obstrução das frentes não será mais tão perceptível devido à forma de onda. No entanto, se a frequência é aumentada, o sinal sinusoidal se torna triangular e, com um aumento adicional, a amplitude do sinal diminui.
Geração a 1 KHz ( 90% de amplitude ):
Geração a 10 KHz ( 90% de amplitude ):
Geração a 100 KHz ( 90% de amplitude ):
As etapas já estão visíveis - porque novos dados são carregados no DAC na frequência de 4 MHz.
Além disso, a borda posterior do sinal dente de serra é confusa e, por baixo do sinal, não atinge o valor ao qual deveria. Isso ocorre porque o sinal não tem tempo para atingir o nível baixo especificado e o software está carregando novos valores
Geração a 200 KHz ( 90% de amplitude ):
Aqui você já pode ver como todas as ondas se transformaram em um triângulo.
Canais digitais
Com os canais digitais, tudo é muito mais simples - em quase todos os microcontroladores, existem temporizadores que permitem emitir um sinal PWM para as saídas do microcontrolador. É melhor usar um cronômetro de 32 bits - nesse caso, não é necessário contar o pré-cronômetro, basta carregar o período em um registro e carregar o ciclo de trabalho necessário em outro registro.
Interface do usuário
Foi decidido organizar a interface do usuário em quatro retângulos, cada um com uma imagem do sinal de saída, frequência e amplitude / ciclo de serviço. Para o canal atualmente selecionado, os dados de texto são exibidos em branco e os demais em cinza.

Decidiu-se controlar os codificadores: o esquerdo é responsável pela frequência e o canal selecionado atual (
muda quando o botão é pressionado ), o direito é responsável pelo ciclo de amplitude / serviço e forma de onda (
muda quando o botão é pressionado ).
Além disso, o suporte à tela de toque é implementado - quando você clica em um canal inativo, ele se torna ativo; quando você clica em um canal ativo, a forma de onda muda.
Obviamente, o DevCore é usado para fazer tudo isso. O código para inicializar a interface do usuário e atualizar os dados na tela é semelhante a este:
Estrutura contendo todos os objetos da interface do usuário Código de inicialização da interface do usuário Código de atualização da tela for(uint32_t i = 0U; i < CHANNEL_CNT; i++) { ch_dsc[i].img.SetImage(waveforms[ch_dsc[i].waveform]); snprintf(ch_dsc[i].freq_str_data, NumberOf(ch_dsc[i].freq_str_data), "Freq: %7lu Hz", ch_dsc[i].frequency); if(IsAnalogChannel(i)) snprintf(ch_dsc[i].duty_str_data, NumberOf(ch_dsc[i].duty_str_data), "Ampl: %7d %%", ch_dsc[i].duty); else snprintf(ch_dsc[i].duty_str_data, NumberOf(ch_dsc[i].duty_str_data), "Duty: %7d %%", ch_dsc[i].duty);
Uma implementação interessante do clique do botão é implementada (
é um retângulo sobre o qual os elementos restantes são desenhados ). Se você analisou o código, deveria ter notado isso:
ch_dsc [i] .box.SetCallback (& Callback, this, nullptr, i); chamado em um loop. Este é o trabalho da função de retorno de chamada que será chamada quando o botão for pressionado. Os seguintes são transferidos para a função: o endereço da função estática da função estática da classe, o ponteiro this e dois parâmetros do usuário que serão passados para a função de retorno de chamada - um ponteiro (
não usado neste caso - nullptr é passado ) e um número (
número do canal é transmitido ).
Da bancada da universidade, lembro-me do postulado: "
Funções estáticas não têm acesso a alunos não estáticos ". Então isso
não é
verdade . Como uma função estática é membro de uma classe, ela
tem acesso a todos os membros da classe se tiver um link / ponteiro para essa classe. Agora, dê uma olhada na função de retorno de chamada:
Na primeira linha desta função, ocorre "
mágica ", após o qual você pode acessar qualquer membro da classe, incluindo os privados.
A propósito, essa função é chamada em outra tarefa (
renderizando a tela ); portanto, dentro dessa função, você precisa cuidar da sincronização. Nesse projeto simples de "
duas noites ", eu não fiz isso, porque nesse caso em particular não é essencial.
Código-fonte do gerador carregado no GitHub:
https://github.com/nickshl/WaveformGeneratorO DevCore agora
está alocado para um repositório separado e incluído como um submódulo.
Bem, por que preciso de um gerador de sinal, ele será publicado no próximo (
ou em um dos seguintes ) artigos.