VHDL 乐曲演奏电路设计
前言
无源蜂鸣器在直流信号下不会响,需要方波驱动。输入不同频率的方波会发出不同音调的声音,方波的幅值决定了声音的响度。
目标
乐曲发生电路在节拍(4Hz)的控制下根据乐谱产生合适的分频系数。分频器根据分频系数对时钟脉冲分频产生各种声调所需要的频率的信号。最后考虑到驱动蜂鸣器要足够的能量使用2分频器对信号进行二分频变为占空比50%的信号。
模块设计
分频器
上面的框图中预分频器、可预置数分频器都是分频器。可以采用上一个设计“数字时钟”设计的“数控N分频器”,不同之处是传入N的位宽(12位)要满足产生音调对应的频率所需分频系数大小(最大3822),通过给分频器传入数值N来对时钟信号进行N分频。得到的信号频率为原时钟信号的频率/N,占空比为1/N。
代码如下
library IEEE;
use IEEE.STD_LOGIC_1164.all;
use IEEE.STD_LOGIC_UNSIGNED.all;
entity ClkDiv is
port(
clk_i:IN STD_LOGIC;
N_i: IN STD_LOGIC_VECTOR(11 DOWNTO 0);
clk_o:OUT STD_LOGIC);
end ClkDiv;
architecture behavior of ClkDiv is
signal count:STD_LOGIC_VECTOR(11 DOWNTO 0):="000000000001";
signal clk_temp:STD_LOGIC:='0';
begin
process(clk_i)
begin
if(clk_i'EVENT and clk_i='1')then
if (count=N_i)then
count<="000000000001";
clk_temp<='1';
else
count<=count+1;
clk_temp<='0';
end if;
end if;
end process;
clk_o<=clk_temp;
end behavior;
仿真结果见“数字时钟设计”
乐曲发生器
乐曲发生电路在节拍(4Hz)的控制下根据乐谱产生合适的分频系数。在分频器输入为1Mhz的情况下,各个音调对应的声音的分频系数如下图所示。虽然我们设计的分频器输入为2Mhz,但是在驱动蜂鸣器前还进行了二分频,所以以下分频系数对我们的设计而言是合适的。
代码如下
--乐曲发生器Director 在4Hz的信号激励下,产生不同的预置数
library IEEE;
use IEEE.STD_LOGIC_1164.all;
use IEEE.STD_LOGIC_ARITH.all;
entity Director is
port(
Director_i : IN STD_LOGIC;
Director_o: OUT STD_LOGIC_VECTOR(11 downto 0)
);
end Director;
architecture rtl of Director is
signal count :integer:=0;
signal s: STD_LOGIC_VECTOR(4 downto 0) :="00000";
signal Director_temp: STD_LOGIC_VECTOR(11 downto 0) :="000000000000";
begin
process(Director_i)
begin
if(Director_i'EVENT and Director_i='1')then
if(count>=47)then
count<=0;
else
count<=count+1;
end if;
if (count=0) then s<="00011"; --乐谱
elsif (count=1)then s<="00011";
elsif (count=2)then s<="00011";
elsif (count=3)then s<="00011";
elsif (count=4)then s<="00100";
elsif (count=5)then s<="00100";
elsif (count=6)then s<="00100";
elsif (count=7)then s<="00101";
elsif (count=8)then s<="00111";
elsif (count=9)then s<="00111";
elsif (count=10)then s<="00111";
elsif (count=11)then s<="01000";
elsif (count=12)then s<="00101";
elsif (count=13)then s<="00111";
elsif (count=14)then s<="00100";
elsif (count=15)then s<="00100";
elsif (count=16)then s<="01011";
elsif (count=17)then s<="01011";
elsif (count=18)then s<="01011";
elsif (count=19)then s<="01110";
elsif (count=20)then s<="01100";
elsif (count=21)then s<="01011";
elsif (count=22)then s<="01001";
elsif (count=23)then s<="01011";
elsif (count=24)then s<="01000";
elsif (count=25)then s<="01000";
elsif (count=26)then s<="01000";
elsif (count=27)then s<="01000";
elsif (count=28)then s<="01000";
elsif (count=29)then s<="01000";
elsif (count=30)then s<="01000";
elsif (count=31)then s<="01000";
elsif (count=32)then s<="01000";
elsif (count=33)then s<="01000";
elsif (count=34)then s<="01000";
elsif (count=35)then s<="01001";
elsif (count=36)then s<="00110";
elsif (count=37)then s<="00110";
elsif (count=38)then s<="00101";
elsif (count=39)then s<="00101";
elsif (count=40)then s<="00100";
elsif (count=41)then s<="00100";
elsif (count=42)then s<="00100";
elsif (count=43)then s<="00101";
elsif (count=44)then s<="00111";
elsif (count=45)then s<="00111";
elsif (count=46)then s<="01000";
elsif (count=47)then s<="01000";
end if;
end if;
end process;
-- process(Director_i)
-- begin
-- if(Director_i'EVENT and Director_i='1')then
-- if(count=9)then
-- count<=0;
-- else
-- count<=count+1;
-- end if;
-- if (count=0) then s<="00000"; --乐谱
-- elsif (count=1)then s<="00001";
-- elsif (count=2)then s<="00011";
-- elsif (count=3)then s<="00100";
-- elsif (count=4)then s<="00101";
-- elsif (count=5)then s<="00110";
-- elsif (count=6)then s<="00111";
-- elsif (count=7)then s<="01000";
-- elsif (count=8)then s<="01001";
-- elsif (count=9)then s<="01010";
-- end if;
-- end if;
-- end process;
process(s)
begin --预分频数
if (S="00000") then Director_temp<="111011101110";--低音1
elsif(S="00001") then Director_temp<="110101001101";--低音2
elsif(S="00010") then Director_temp<="101111011010";
elsif(S="00011") then Director_temp<="101100101111";
elsif(S="00100") then Director_temp<="100111110111";
elsif(S="00101") then Director_temp<="100011100001";
elsif(S="00110") then Director_temp<="011111101001";
elsif(S="00111") then Director_temp<="011101110111";--中音1
elsif(S="01000") then Director_temp<="011010100111";
elsif(S="01001") then Director_temp<="010111101101";
elsif(S="01010") then Director_temp<="010110011000";
elsif(S="01011") then Director_temp<="010011111100";
elsif(S="01100") then Director_temp<="010001110000";
elsif(S="01101") then Director_temp<="001111110100";
elsif(S="01110") then Director_temp<="001110111100";--高音1
elsif(S="01111") then Director_temp<="001101010011";
elsif(S="10000") then Director_temp<="001011110110";
elsif(S="10001") then Director_temp<="001011001100";
elsif(S="10010") then Director_temp<="001001111110";
elsif(S="10011") then Director_temp<="001000111000";
elsif(S="10100") then Director_temp<="000111111010";
end if;
end process;
Director_o<=Director_temp;
end rtl;
仿真结果如下,可以看到在4Hz输入信号的激励下,输出乐谱对应的分频系数
顶层模块
先对输入的时间脉冲33.8688MHz进行16分频得到2MHz的时间脉冲输入数控分频器。使用同样的方法得到4Hz脉冲输入乐曲发生电路得到音调对应的分频系数,把分频系数输入数控分频器得到音调对应的频率的信号,再对此信号二分频以增强驱动能力。
代码如下
library IEEE;
use IEEE.STD_LOGIC_1164.all;
entity TonePlayer is
port(
TonePlayerClk_i:IN STD_LOGIC;
TonePlayerBeep_o:OUT STD_LOGIC);
end TonePlayer;
architecture rtl of TonePlayer is
component ClkDiv is
port(
clk_i:IN STD_LOGIC;
N_i: IN STD_LOGIC_VECTOR(11 DOWNTO 0);
clk_o:OUT STD_LOGIC);
end component;
component Director is
port(
Director_i : IN STD_LOGIC;
Director_o: OUT STD_LOGIC_VECTOR(11 downto 0)
);
end component;
signal N1: STD_LOGIC_VECTOR(11 DOWNTO 0):="000000011001";
signal Clk2M: STD_LOGIC:='0';
signal N2: STD_LOGIC_VECTOR(11 DOWNTO 0):="001111101000";
signal Clk2K: STD_LOGIC:='0';
signal N3: STD_LOGIC_VECTOR(11 DOWNTO 0):="000111110100";
signal Clk4Hz: STD_LOGIC:='0';
signal DirectorTemp: STD_LOGIC_VECTOR(11 downto 0):="000000000000";
signal N4: STD_LOGIC_VECTOR(11 DOWNTO 0):="000000000010";
signal TonePlayerBeep_Temp: STD_LOGIC:='0';
begin
Clock_Map: ClkDiv port map(
clk_i=>TonePlayerClk_i,
N_i=>N1,
clk_o=>Clk2M);
Clock_Map2: ClkDiv port map(
clk_i=>Clk2M,
N_i=>N2,
clk_o=>Clk2K);
Clock_Map3: ClkDiv port map(
clk_i=>Clk2K,
N_i=>N3,
clk_o=>Clk4Hz);
Director_Map: Director port map(
Director_i =>Clk4Hz,
Director_o=>DirectorTemp);
CORE_Map4: ClkDiv port map(
clk_i=>Clk2M,
N_i=>DirectorTemp,
clk_o=>TonePlayerBeep_Temp);
Drive_Map: ClkDiv port map(
clk_i=>TonePlayerBeep_Temp,
N_i=>N4,
clk_o=>TonePlayerBeep_o);
end rtl;
仿真结果,DirectorTemp指示了当前输出的音调,可以看到在音调的分界线处,驱动蜂鸣器的TonePlayerBeep_o频率明显改变。而且占空比为50%具有较强的驱动能力