Índice:

Sintetizador de áudio digital Basys3 FPGA: 5 etapas
Sintetizador de áudio digital Basys3 FPGA: 5 etapas

Vídeo: Sintetizador de áudio digital Basys3 FPGA: 5 etapas

Vídeo: Sintetizador de áudio digital Basys3 FPGA: 5 etapas
Vídeo: AMD Xilinx Arty A7, Artix 7 FPGA Evaluation Board - Getting Started 2024, Novembro
Anonim
Image
Image
Sintetizador de áudio digital Basys3 FPGA
Sintetizador de áudio digital Basys3 FPGA
Sintetizador de áudio digital Basys3 FPGA
Sintetizador de áudio digital Basys3 FPGA

Este sintetizador de teclado digital de onda senoidal recebe as entradas do usuário por meio de uma série de interruptores momentâneos dispostos como um teclado e produz uma onda de áudio por meio de um alto-falante. Com base nas entradas do usuário, o dispositivo irá gerar ondas senoidais de várias frequências de C4 a C6. O usuário pode inserir notas de C4 a C6 (total de 25 notas) e até quatro teclas ao mesmo tempo - se mais de quatro teclas forem pressionadas, os quatro tons mais baixos serão reproduzidos.

Este projeto foi realizado por Ryan Morris e Mavis Tsoi para nossa aula de design digital Cal Poly CPE 133:)

Etapa 1: Teoria

Uma placa FPGA pode emitir apenas sinais digitais. Em outras palavras, ele só pode produzir uma tensão alta (3,3 V) ou uma tensão baixa (0 V). No entanto, os sinais de áudio são analógicos e podem ter infinitos incrementos de voltagem. Para contornar isso, usaremos um sinal PWM (modulação por largura de pulso) para emular uma onda analógica. Se você não sabe o que é PWM, verifique:

Etapa 2: Ingredientes e ferramentas

  • Computador com Vivado instalado
  • Estaremos usando Vivado versão 2017.2
  • Placa FPGA Basys3
  • 25 Chaves de limite SPDT (nós as usamos)
  • 30 fios de jumper (uma extremidade macho, a outra extremidade não importa), 12 polegadas
  • Cortadores de arame
  • Decapantes de arame
  • Fio sobressalente para solda
  • Solda com núcleo de resina
  • Ferro de solda
  • Entrada de áudio fêmea de ¼”
  • Amplificador / alto-falante
  • Algo para montar os interruptores (usamos protoboard + caixa de madeira)

Etapa 3: Instalação de fiação e hardware

Instalação de fiação e hardware
Instalação de fiação e hardware
Instalação de fiação e hardware
Instalação de fiação e hardware
Instalação de fiação e hardware
Instalação de fiação e hardware

Arquitetura do Sistema

Veja a Figura 1: 25 entradas disponíveis → Placa Basys3 → amplificador e alto-falante.

Saída

Veja a Figura 2: Placa Basys3 → 1/2 Conector de áudio feminino → Alto-falante (com amplificador)

Entrada

As conexões pmod na placa Basys3 devem ser conectadas ao aterramento para ver uma entrada baixa e não funcionarão corretamente se deixadas como um circuito aberto. Por causa disso, temos que usar interruptores SPDT para todas as nossas teclas de nota. Uma chave SPDT basicamente permite que o usuário alterne entre os circuitos quando pressionada, então vamos usá-los como nossos “botões” para inserir sinais baixos (0V) ou altos (3,3V) para a placa Basys3.

Cada chave terá o terminal NO (normalmente aberto) conectado a 3,3 V, o terminal NC (normalmente fechado) conectado ao GND e o terminal COM (comum) conectado à entrada do FPGA. Veja a Figura 3.

Como temos 25 interruptores de limite, todos compartilharão uma linha comum de 3,3 V e uma linha GND comum. Em seguida, a linha de sinal de cada interruptor de limite será agrupada em grupos de 8 e ligada às conexões pmod na placa Basys3 usando fios de jumper zippable para minimizar a confusão monumental que faremos. Veja a Figura 4 ou um exemplo das primeiras oito chaves.

Etapa 4: configuração de VHDL (Vivado)

Configuração VHDL (Vivado)
Configuração VHDL (Vivado)
Configuração VHDL (Vivado)
Configuração VHDL (Vivado)

O gerador de onda senoidal e o gerador PWM foram testados primeiro para garantir que nosso conceito funcionasse, depois o limitador de entrada e o somador / deslocador de amplitude foram integrados. Os detalhes da função e E / S de cada bloco de processo são mostrados na Figura. O código é mostrado abaixo, mas também anexado como arquivos VHD e txt. Se houver discrepâncias, vá com os arquivos VHD.

BTW: provavelmente deveríamos ter encurtado nossas linhas, mas a incorporação de código em Instructables também se tornou um tanto chata de lidar, então o espaçamento não é o maior e não há realce de sintaxe. Se você possui o Vivado e gostaria de acompanhar o código, recomendamos que você apenas baixe o arquivo.

Primeiro, vamos dar uma olhada no módulo gerador de onda senoidal.

biblioteca IEEE; use IEEE. STD_LOGIC_1164. ALL; use IEEE. NUMERIC_STD. ALL; entidade Wave_Generator é a porta (Trigger: em STD_LOGIC; - Pressionar tecla Freq_Cnt: em STD_LOGIC_VECTOR (15 até 0); - Valor do contador = 100 MHz / (Nota de frequência * 64 divisões da onda sinusoidal) (arredondado para o número mais próximo) - renomeado de Freq wavegenCLK: em STD_LOGIC; - Basys3 100 MHz CLK WaveOut: fora STD_LOGIC_VECTOR (9 para 0)); - Amplitude sinalizada de final de onda Wave_Generator; arquitetura O comportamento de Wave_Generator é o sinal i: intervalo inteiro de 0 a 64: = 0; - o índice de amplitude do tipo de banco de memória memory_type é uma matriz (0 a 63) de intervalo inteiro de -64 a 63; - crie banco de memória (ROM) para manter os valores de amplitude - esta RAM ou ROM está apenas imaginando … amplitude do sinal: memory_type: = (0, 7, 13, 19, 25, 30, 35, 40, 45, 49, 52, 55, 58, 60, 62, 63, 63, 63, 62, 60, 58, 55, 52, 49, 45, 40, 35, 30, 25, 19, 13, 7, 0, -7, -13, -19, -25, -30, -35, -40, -45, -49, -52, -55, -58, -60, -62, -63, -63, -63, -62, - 60, -58, -55, -52, -49, -45, -40, -35, -30, -25, -19, -13, -7); - banco de memória de amplitude para o processo de início da onda senoidal (wavegenCLK, Trigger) contador de variável: sem sinal (15 até 0): = to_unsigned (0, 16); - contador divisor de relógio, renomeado de count1 begin if (rise_edge (wavegenCLK)) then if (Trigger = '1') then - tecla é pressionada counter: = counter + 1; if (counter = unsigned (Freq_Cnt)) then - Freq_Cnt = 100Mhz / (note freq * 64 divisões da onda senoidal) - reinicie o contador e atribua os dados de amplitude ao contador de saída: = to_unsigned (0, 16); WaveOut <= STD_LOGIC_VECTOR (to_signed (amplitude (i), 10)); - incremento i para a próxima leitura i <= i + 1; - redefinir i se uma onda senoidal foi concluída se (i = 63) então i <= 0; fim se; fim se; - (contador = sem sinal (Freq_Cnt)) else - a tecla não foi pressionada - reinicializar a saída, o índice de amplitude e o contador WaveOut <= "0000000000"; i <= 0; contador: = to_unsigned (0, 16); --output Amplitude = -64 quando nenhuma nota é tocada termina se; - (Trigger = '1') fim se; - processo de finalização (crescente_edge (CLK)); fim Comportamental;

Iremos gerar uma onda senoidal digital no Basys3 usando o relógio interno e um ROM. Esta ROM armazenará 64 valores que representam 64 amplitudes em uma onda senoidal. Veja a Figura 1. Os 64 valores que usamos emulam uma onda senoidal com resolução muito boa.

Usando o relógio interno, contamos até um valor que representa a velocidade do relógio dividida pela frequência da onda que queremos e 64: Clk div = 100 MHz / (Freq * 64) Cada vez que nosso contador atinge esse valor, chamamos um número de a ROM e envie-a de nosso módulo gerador de ondas. A frequência de nossa onda dependerá de quão rápido chamamos essas amplitudes.

Teremos 25 submódulos, cada um associado a uma frequência / nota.

Aqui está o restante do código que chama os módulos do gerador de onda senoidal:

biblioteca IEEE; use IEEE. STD_LOGIC_1164. ALL; use IEEE. NUMERIC_STD. ALL; entidade Two_Octave_Synth é a porta (CLK: em STD_LOGIC; O4: em STD_LOGIC_VECTOR (11 para 0); O5: em STD_LOGIC_VECTOR (12 para 0); saída: para fora STD_LOGIC); fim Two_Octave_Synth; arquitetura Behavioral of Two_Octave_Synth é o componente Wave_Generator é Port (Trigger: in STD_LOGIC; Freq_Cnt: in STD_LOGIC_VECTOR (15 downto 0); wavegenCLK: in STD_LOGIC; WaveOut: out STD_LOGIC_VECTOR (9 downto 0)); componente final; --------------------------- sinais de saída do gerador de onda ------------------ ----- sinal WaveC4, WaveCs4, WaveD4, WaveDs4, WaveE4, WaveF4, WaveFs4, WaveG4, WaveGs4, WaveA4, WaveAs4, WaveB4, WaveC5, WaveCs5, WaveD5, WaveDs5, WaveE5, WaveF5, WaveFs5, WaveG5, WaveGs5, WaveA5, WaveA5, WaveAs5, WaveB5, WaveC6: assinado (9 até 0); -------------------------------- para lógica de seleção de notas -------------- ------ sinal C4, Cs4, D4, Ds4, E4, F4, Fs4, G4, Gs4, A4, As4, B4, C5, Cs5, D5, Ds5, E5, F5, Fs5, G5, Gs5, A5, As5, B5, C6: sem sinal (4 até 0); sinal cntC4, cntCs4, cntD4, cntDs4, cntE4, cntF4, cntFs4, cntG4, cntGs4, cntA4, cntAs4, cntB4, cntC5, cntCs5, cntD5, cntDs5, cntE5, cntF5, cntFs5, cntG5, cntGs5, cntA5, cntAs5, cntB5, cntC6: sem sinal (4 até 0); erro de sinal: STD_LOGIC; ----------------------------------- para adicionar ondas senoidais ----------- --------------- sinal Wave0, Wave1, Wave2, Wave3: assinado (9 até 0); --sinais do sinal de saída do módulo WaveSum do módulo WaveSum: STD_LOGIC_VECTOR (9 até 0); --sinal para ondas senoidais somadas (complemento de 2 -512 a 511) sinal positivoWaveSum: STD_LOGIC_VECTOR (9 até 0); --unsigned 0 a 1023, para uso no gerador PWM ----------------------------------- para gerar PWM ------------------------------- sinal ping_length: sem sinal (9 para 0): = sem sinal (positiveWaveSum); --signal off_length: unsigned (6 downto 0): = to_unsigned (127, 7) - unsigned (WAVE); sinal PWM: não assinado (9 até 0): = até_insignado (0, 10); começar Note_C4: Mapa da porta Wave_Generator (Trigger => O4 (0), Freq_Cnt => X "1755", wavegenCLK => CLK, assinado (WaveOut) => WaveC4); --5973, 261,63 Hz Note_Cs4: Mapa da porta Wave_Generator (Trigger => O4 (1), Freq_Cnt => X "1606", wavegenCLK => CLK, assinado (WaveOut) => WaveCs4); - 5638, 277,18 Hz Note_D4: Mapa da porta Wave_Generator (Trigger => O4 (2), Freq_Cnt => X "14C9", wavegenCLK => CLK, assinado (WaveOut) => WaveD4); --5321, 293,66 Hz Note_Ds4: Mapa da porta Wave_Generator (Trigger => O4 (3), Freq_Cnt => X "139F", wavegenCLK => CLK, assinado (WaveOut) => WaveDs4); - 5023, 311,13 Hz Note_E4: Mapa da porta Wave_Generator (Trigger => O4 (4), Freq_Cnt => X "1285", wavegenCLK => CLK, assinado (WaveOut) => WaveE4); --4741, 329,63 Hz Note_F4: Mapa da porta Wave_Generator (Trigger => O4 (5), Freq_Cnt => X "117B", wavegenCLK => CLK, assinado (WaveOut) => WaveF4); - 4475, 349,23 Hz Note_Fs4: Mapa da porta Wave_Generator (Trigger => O4 (6), Freq_Cnt => X "1080", wavegenCLK => CLK, assinado (WaveOut) => WaveFs4); - 4224, 369,99 Hz Note_G4: Mapa da porta Wave_Generator (Trigger => O4 (7), Freq_Cnt => X "0F92", wavegenCLK => CLK, assinado (WaveOut) => WaveG4); --3986, 392,00 Hz Note_Gs4: Mapa da porta Wave_Generator (Trigger => O4 (8), Freq_Cnt => X "0EB3", wavegenCLK => CLK, assinado (WaveOut) => WaveGs4); - 3763, 415,30 Hz Note_A4: Mapa da porta Wave_Generator (Trigger => O4 (9), Freq_Cnt => X "0DE0", wavegenCLK => CLK, assinado (WaveOut) => WaveA4); --3552, 440,00 Hz Note_As4: Mapa da porta Wave_Generator (Trigger => O4 (10), Freq_Cnt => X "0D18", wavegenCLK => CLK, assinado (WaveOut) => WaveAs4); - 3352, 466,16 Hz Note_B4: Mapa da porta Wave_Generator (Trigger => O4 (11), Freq_Cnt => X "0C5C", wavegenCLK => CLK, assinado (WaveOut) => WaveB4); - 3164, 493,88 Hz -------------------------------------------- -------------------------------------------------- --------------------------- Note_C5: Mapa da porta Wave_Generator (Trigger => O5 (0), Freq_Cnt => X "0BAB", wavegenCLK => CLK, assinado (WaveOut) => WaveC5); --2987, 523,25 Hz Note_Cs5: Mapa da porta Wave_Generator (Trigger => O5 (1), Freq_Cnt => X "0B03", wavegenCLK => CLK, assinado (WaveOut) => WaveCs5); - 2819, 554,37 Hz Note_D5: Mapa da porta Wave_Generator (Trigger => O5 (2), Freq_Cnt => X "0A65", wavegenCLK => CLK, assinado (WaveOut) => WaveD5); --2661, 587,33 Hz Note_Ds5: Mapa da porta Wave_Generator (Trigger => O5 (3), Freq_Cnt => X "09D0", wavegenCLK => CLK, assinado (WaveOut) => WaveDs5); - 2512, 622,25 Hz Note_E5: Mapa da porta Wave_Generator (Trigger => O5 (4), Freq_Cnt => X "0943", wavegenCLK => CLK, assinado (WaveOut) => WaveE5); --2371, 659,25 Hz Note_F5: Mapa da porta Wave_Generator (Trigger => O5 (5), Freq_Cnt => X "08Be", wavegenCLK => CLK, assinado (WaveOut) => WaveF5); --2238, 698,46 Hz Note_Fs5: Mapa da porta Wave_Generator (Trigger => O5 (6), Freq_Cnt => X "0840", wavegenCLK => CLK, assinado (WaveOut) => WaveFs5); - 2112, 739,99 Hz Note_G5: Mapa da porta Wave_Generator (Trigger => O5 (7), Freq_Cnt => X "07CA", wavegenCLK => CLK, assinado (WaveOut) => WaveG5); - 1994, 783,99 Hz Note_Gs5: Mapa da porta Wave_Generator (Trigger => O5 (8), Freq_Cnt => X "075A", wavegenCLK => CLK, assinado (WaveOut) => WaveGs5); - 1882, 830,61 Hz Note_A5: Mapa da porta Wave_Generator (Trigger => O5 (9), Freq_Cnt => X "06F0", wavegenCLK => CLK, assinado (WaveOut) => WaveA5); --1776, 880,00 Hz Note_As5: Mapa da porta Wave_Generator (Trigger => O5 (10), Freq_Cnt => X "068C", wavegenCLK => CLK, assinado (WaveOut) => WaveAs5); - 1676, 932,33 Hz Note_B5: Mapa da porta Wave_Generator (Trigger => O5 (11), Freq_Cnt => X "062E", wavegenCLK => CLK, assinado (WaveOut) => WaveB5); --1582, 987,77 Hz Note_C6: Mapa da porta Wave_Generator (Trigger => O5 (12), Freq_Cnt => X "05D6", wavegenCLK => CLK, assinado (WaveOut) => WaveC6); --1494, 1046,5 Hz ------------ lógica de seleção de notas ------------ C4 <= "0000" & O4 (0); Cs4 <= "0000" & O4 (1); D4 <= "0000" & O4 (2); Ds4 <= "0000" & O4 (3); E4 <= "0000" & O4 (4); F4 <= "0000" & O4 (5); Fs4 <= "0000" & O4 (6); G4 <= "0000" & O4 (7); Gs4 <= "0000" & O4 (8); A4 <= "0000" e O4 (9); As4 <= "0000" & O4 (10); B4 <= "0000" & O4 (11); C5 <= "0000" & O5 (0); Cs5 <= "0000" & O5 (1); D5 <= "0000" & O5 (2); Ds5 <= "0000" & O5 (3); E5 <= "0000" & O5 (4); F5 <= "0000" & O5 (5); Fs5 <= "0000" & O5 (6); G5 <= "0000" & O5 (7); Gs5 <= "0000" & O5 (8); A5 <= "0000" & O5 (9); As5 <= "0000" & O5 (10); B5 <= "0000" & O5 (11); C6 <= "0000" & O5 (12); cntC4 <= C4; cntCs4 <= C4 + Cs4; cntD4 <= C4 + Cs4 + D4; cntDs4 <= C4 + Cs4 + D4 + Ds4; cntE4 <= C4 + Cs4 + D4 + Ds4 + E4; cntF4 <= C4 + Cs4 + D4 + Ds4 + E4 + F4; cntFs4 <= C4 + Cs4 + D4 + Ds4 + E4 + F4 + Fs4; cntG4 <= C4 + Cs4 + D4 + Ds4 + E4 + F4 + Fs4 + G4; cntGs4 <= C4 + Cs4 + D4 + Ds4 + E4 + F4 + Fs4 + G4 + Gs4; cntA4 <= C4 + Cs4 + D4 + Ds4 + E4 + F4 + Fs4 + G4 + Gs4 + A4; cntAs4 <= C4 + Cs4 + D4 + Ds4 + E4 + F4 + Fs4 + G4 + Gs4 + A4 + As4; cntB4 <= C4 + Cs4 + D4 + Ds4 + E4 + F4 + Fs4 + G4 + Gs4 + A4 + As4 + B4; cntC5 <= C4 + Cs4 + D4 + Ds4 + E4 + F4 + Fs4 + G4 + Gs4 + A4 + As4 + B4 + C5; cntCs5 <= C4 + Cs4 + D4 + Ds4 + E4 + F4 + Fs4 + G4 + Gs4 + A4 + As4 + B4 + C5 + Cs5; cntD5 <= C4 + Cs4 + D4 + Ds4 + E4 + F4 + Fs4 + G4 + Gs4 + A4 + As4 + B4 + C5 + Cs5 + D5; cntDs5 <= C4 + Cs4 + D4 + Ds4 + E4 + F4 + Fs4 + G4 + Gs4 + A4 + As4 + B4 + C5 + Cs5 + D5 + Ds5; cntE5 <= C4 + Cs4 + D4 + Ds4 + E4 + F4 + Fs4 + G4 + Gs4 + A4 + As4 + B4 + C5 + Cs5 + D5 + Ds5 + E5; cntF5 <= C4 + Cs4 + D4 + Ds4 + E4 + F4 + Fs4 + G4 + Gs4 + A4 + As4 + B4 + C5 + Cs5 + D5 + Ds5 + E5 + F5; cntFs5 <= C4 + Cs4 + D4 + Ds4 + E4 + F4 + Fs4 + G4 + Gs4 + A4 + As4 + B4 + C5 + Cs5 + D5 + Ds5 + E5 + F5 + Fs5; cntG5 <= C4 + Cs4 + D4 + Ds4 + E4 + F4 + Fs4 + G4 + Gs4 + A4 + As4 + B4 + C5 + Cs5 + D5 + Ds5 + E5 + F5 + Fs5 + G5; cntGs5 <= C4 + Cs4 + D4 + Ds4 + E4 + F4 + Fs4 + G4 + Gs4 + A4 + As4 + B4 + C5 + Cs5 + D5 + Ds5 + E5 + F5 + Fs5 + G5 + Gs5; cntA5 <= C4 + Cs4 + D4 + Ds4 + E4 + F4 + Fs4 + G4 + Gs4 + A4 + As4 + B4 + C5 + Cs5 + D5 + Ds5 + E5 + F5 + Fs5 + G5 + Gs5 + A5; cntAs5 <= C4 + Cs4 + D4 + Ds4 + E4 + F4 + Fs4 + G4 + Gs4 + A4 + As4 + B4 + C5 + Cs5 + D5 + Ds5 + E5 + F5 + Fs5 + G5 + Gs5 + A5 + As5; cntB5 <= C4 + Cs4 + D4 + Ds4 + E4 + F4 + Fs4 + G4 + Gs4 + A4 + As4 + B4 + C5 + Cs5 + D5 + Ds5 + E5 + F5 + Fs5 + G5 + Gs5 + A5 + As5 + B5; cntC6 <= C4 + Cs4 + D4 + Ds4 + E4 + F4 + Fs4 + G4 + Gs4 + A4 + As4 + B4 + C5 + Cs5 + D5 + Ds5 + E5 + F5 + Fs5 + G5 + Gs5 + A5 + As5 + B5 + C6; Seleção: processo (WaveC4, WaveCs4, WaveD4, WaveDs4, WaveE4, WaveF4, WaveFs4, WaveG4, WaveGs4, WaveA4, WaveAs4, WaveB4, WaveC5, WaveCs5, WaveD5, WaveDs5, WaveE5, WaveF5, WaveFs5, WaveG5, WaveGs5, WaveA WaveB5, WaveC6) começa se (cntC6 = "00000") então --------------- se nenhum sinal está sendo gerado Wave0 <= "0000000000"; Wave1 <= "0000000000"; Wave2 <= "0000000000"; Wave3 <= "0000000000"; else if (O4 (0) = '1') then ------------------- nota C4 reproduzida Onda0 Onda0 Onda1 erro Onda0 Onda1 Onda2 erro Onda0 Onda1 Onda2 Onda3 erro Onda0 Onda1 Erro Onda2 Onda3 Erro Onda0 Onda1 Onda2 Onda3 erro Onda0 Onda1 Onda2 Onda3 erro Onda0 Onda1 Onda2 Onda3 erro Onda0 Onda1 Onda2 Onda3 erro Onda0 Onda1 Onda2 Onda3 erro Onda0 Onda1 Onda2 Onda3 erro Onda0 Onda1 Onda2 Onda3 erro Onda0 Onda1 Onda2 Onda3 erro Onda0 Onda1 Onda2 Onda3 erro Onda0 Onda1 Onda2 Onda3 erro Onda0 Onda1 Onda2 Onda3 erro Onda0 Onda1 Onda2 Onda3 erro Onda0 Onda1 Onda2 Onda3 erro Onda0 Onda1 Onda2 Onda3 erro Onda0 Onda1 Onda2 Onda3 erro Onda0 Onda1 Onda2 Onda3 erro Onda0 Onda1 Onda2 Onda3 erro Onda0 Onda1 Onda2 Onda3 erro Erro Onda2 Onda3 Erro Onda0 Onda1 Onda2 Onda3 erro Onda0 Onda1 Onda2 Onda3 erro Onda0 Onda1 Onda2 Onda3 erro Onda0 Onda1 Onda2 Onda3 erro Onda0 Onda1 Onda2 erro Onda3 erro Onda0 Onda1 Onda2 Onda3 erro Onda0 Onda1 Onda2 Onda3 erro Onda0 Onda1 Onda2 Onda3 erro Onda0 Onda1 Onda2 Onda3 erro Onda0 < = WaveC6; Wave1 <= "0000000000"; Wave2 <= "0000000000"; Wave3 Wave1 <= WaveC6; Wave2 <= "0000000000"; Onda3 Onda2 <= OndaC6; Erro Wave3 Wave3 Wave1 <= "0000000000"; Wave2 <= "0000000000"; Wave3 Wave2 <= "0000000000"; Erro Wave3 Wave3 <= '1'; caso final; fim se; fim se; fim do processo; ------------- somador de onda senoidal -------------------- WaveSum <= STD_LOGIC_VECTOR (Onda0 + Onda1 + Onda2 + Onda3); --------- tornar a onda senoidal positiva para pwm --------------------- positiveWaveSum <= não WaveSum (9) & WaveSum (8 downto 0); ------------- gerador PWM --------------------- processo (CLK) --contagem variável: não assinado (1 até 0): = to_unsigned (0, 2); começar if (rise_edge (CLK)) then --count: = count + 1; --if (contagem = to_unsigned (4, 2)) then --count: = to_unsigned (0, 2); --if (PWM = to_ if (PWM <ping_length) then output <= '1'; else output <= '0'; end if; PWM <= PWM + 1; ping_length <= unsigned (positiveWaveSum); --end if; end if; end process; end Behavioral;

4 Seletor de notasA parte mais complicada deste projeto é selecionar apenas quatro frequências. Fizemos isso com uma grande quantidade de instruções IF e usamos sinais em vez de variáveis para que o processo pudesse ser simulado e depurado. Tentamos outros métodos usando variáveis e loops FOR, mas encontramos erros em tempo de execução. Então, no final, decidimos que, se funcionar, vamos deixar pra lá. Não conserte o que não está quebrado?

As quatro ondas de saída são rotuladas Onda0, Onda1, Onda2, Onda3 - essas são as que serão somadas para formar a saída final.

Olhando para o código, você verá um monte de sinais rotulados C4, Cs4, D4, Ds4, etc. Estes são sinais de 5 bits que pegam o gatilho correspondente de O4 (oitava 4) ou O5 (oitava 5) e os tornam 5 bits para adicionar.

Em seguida, as variáveis cntC4, cntCs4, etc. representam quantas notas abaixo da nota alvo foram tocadas, incluindo a nota alvo. Por exemplo, se C4, E4, G4, A # 4 e D5 são tocados (acorde C9), cntC4 será 1, cntE4 será 2, cntG4 será 3, etc.

Então, sempre que uma nota é tocada, a contagem da nota alvo será examinada para ver onde conectar o sinal da nota. Por exemplo, se a nota D5 é tocada (o que significa O5 (2) é alto) e cntD5 é 3, então existem atualmente 3 notas sendo tocadas, com 2 notas mais baixas do que D5, então ligaremos a onda D5 à Wave2 (a terceira onda contagem de sinal de Wave0). Alternativamente, se cntD5 for 5, então existem atualmente 5 notas sendo tocadas, com 4 notas abaixo de D5, portanto, deixaremos o waveD5 suspenso e não faremos nada com ele.

As declarações IF são então repetidas para cobrir os casos de todas as 25 notas.

Amplitude Adder

Depois que as 4 ondas mais baixas são selecionadas, temos que adicioná-las. O motivo pelo qual adicionaremos apenas quatro notas juntas é porque a ideia do PWM que estamos usando para nossa saída pode ter apenas uma determinada resolução até que o PWM esteja lento demais e o alto-falante comece a captar a onda quadrada do PWM. Por exemplo, se formos usar uma resolução de 8192 (13 bits), cada um desses 8192 pontos deve corresponder a uma borda ascendente do relógio integrado. Portanto, 100 MHz / 8192 = 12,2 kHz, o que está bem dentro da faixa de audição humana.

A adição real das amplitudes é super simples, você só precisa ter certeza de que pode funcionar muito rápido.

Saída PWM

O ciclo de trabalho do PWM representará a amplitude de nossa onda de saída naquele instante. Por exemplo, se tivermos uma faixa de amplitude de 0 a 128, 0 seria um ciclo de trabalho de 0%, 64 seria 50%, 128 seria 100%, etc. Este PWM funcionará extremamente rápido (o nosso é 97,6 kHz), tão rápido que o alto-falante não reconhecerá as ondas quadradas individuais e, em vez disso, olhará para a voltagem média, criando nosso sinal “analógico”.

Arquivo de restrições

Você pode ter conectado seu hardware de maneira diferente, então certifique-se de que o arquivo de restrições corresponda.

Etapa 5: Downloads de código

Abaixo está o código, tanto no formato.txt quanto.vhd para Vivado. Wave_Generator é o submódulo gerador de ondas, e Two_Octave_Synth é o módulo superior com todo o resto.

Recomendado: