1. 程式人生 > >DE2實踐之WM8731產生正弦波

DE2實踐之WM8731產生正弦波


經歷近三週的時間,終於搞定了wm8731產生1khz正弦波的那個DEMO。現在就將這三週來的收穫做一個記錄,希望大家與我共同分享,共同進步。

一.本DEMO 的目的
通過fpga控制音訊編解碼晶片wm8731產生一個1khz的正弦波,接上de2板上的耳機介面,試聽此正弦波。

二.原理
FPGA與wm8731共有5個介面,分別為SCLK,SDIN,DACLRC,DACDAT,BCLK.其中SCLK,SDIN為控制介面,DACLRC,DACDAT,BCLK為數字音訊介面。由SCLK,SDIN配置wm8731的暫存器,由DACLRC,DACDAT,BCLK產生所需的正弦波並輸出到耳機介面。詳細原理見程式碼中註釋。
程式碼如下
1. sclk,sdin資料傳輸時序程式碼(i2c寫控制程式碼)

module i2c_com(clock_i2c, //wm8731控制介面傳輸所需時鐘,0-400khz,此處為20khz
reset_n, 
ack, //應答訊號
i2c_data, //sdin介面傳輸的24位資料
start, //開始傳輸標誌
tr_end, //傳輸結束標誌
cyc_count, 
i2c_sclk, //FPGA與wm8731時鐘介面
i2c_sdat); //FPGA與wm8731資料介面
input [23:0]i2c_data;
input reset_n;
input clock_i2c;
output [5:0]cyc_count;
output ack;
input start;
output tr_end;
output i2c_sclk;
inout i2c_sdat;
reg [5:0] cyc_count;
reg reg_sdat;
reg sclk;
reg ack1,ack2,ack3;
reg tr_end;


wire i2c_sclk;
wire i2c_sdat;
wire ack;

assign ack=ack1|ack2|ack3;
assign i2c_sclk=sclk|(((cyc_count>=4)&(cyc_count<=30))?~clock_i2c:0);
assign i2c_sdat=reg_sdat?1'bz:0;


[email protected]
(posedge clock_i2c or negedge reset_n) begin if(!reset_n) cyc_count<=6'b111111; else begin if(start==0) cyc_count<=0; else if(cyc_count<6'b111111) cyc_count<=cyc_count+1; end end [email protected](posedge clock_i2c or negedge reset_n) begin if(!reset_n) begin tr_end<=0; ack1<=1; ack2<=1; ack3<=1; sclk<=1; reg_sdat<=1; end else case(cyc_count) 0:begin ack1<=1;ack2<=1;ack3<=1;tr_end<=0;sclk<=1;reg_sdat<=1;end 1:reg_sdat<=0; //開始傳輸 2:sclk<=0; 3:reg_sdat<=i2c_data[23]; 4:reg_sdat<=i2c_data[22]; 5:reg_sdat<=i2c_data[21]; 6:reg_sdat<=i2c_data[20]; 7:reg_sdat<=i2c_data[19]; 8:reg_sdat<=i2c_data[18]; 9:reg_sdat<=i2c_data[17]; 10:reg_sdat<=i2c_data[16]; 11:reg_sdat<=1; //應答訊號 12:begin reg_sdat<=i2c_data[15];ack1<=i2c_sdat;end 13:reg_sdat<=i2c_data[14]; 14:reg_sdat<=i2c_data[13]; 15:reg_sdat<=i2c_data[12]; 16:reg_sdat<=i2c_data[11]; 17:reg_sdat<=i2c_data[10]; 18:reg_sdat<=i2c_data[9]; 19:reg_sdat<=i2c_data[8]; 20:reg_sdat<=1; //應答訊號 21:begin reg_sdat<=i2c_data[7];ack2<=i2c_sdat;end 22:reg_sdat<=i2c_data[6]; 23:reg_sdat<=i2c_data[5]; 24:reg_sdat<=i2c_data[4]; 25:reg_sdat<=i2c_data[3]; 26:reg_sdat<=i2c_data[2]; 27:reg_sdat<=i2c_data[1]; 28:reg_sdat<=i2c_data[0]; 29:reg_sdat<=1; //應答訊號 30:begin ack3<=i2c_sdat;sclk<=0;reg_sdat<=0;end 31:sclk<=1; 32:begin reg_sdat<=1;tr_end<=1;end endcase end endmodule


這裡需要關鍵注意的是i2c的寫控制時序要把握準確:個人總結如下圖:


說明:
A:這裡的i2c控制器採用33個i2c時鐘週期進行,其中3-10,12-19,21-28傳送資料,11,20,29為應答位。
B:空閒狀態時,sdat為高阻態,sclk為高電平狀態。
C:每傳送1個位元組的資料,就應返回一個應答訊號將sdat狀態拉低。
D:實際傳輸時,24位的資料各部分為下:
a. 第1個位元組的前7位為wm8731器件地址,第8位為讀或寫選擇位,寫為0,讀為1.
b. 第2個位元組的前7位為wm8731中暫存器的地址位,第8位為暫存器中9位資料的最高位。
c. 第3個位元組的8位資料為wm8731中暫存器中9位資料的低8位。

1. wm8731中暫存器的配置程式

module reg_config(clock_50m,
i2c_sclk,
i2c_sdat,
reset_n);
input clock_50m;
input reset_n;
output i2c_sclk;
inout i2c_sdat;

reg clock_20k;
reg [15:0]clock_20k_cnt;
reg [1:0]config_step;
reg [3:0]reg_index;
reg [23:0]i2c_data;
reg [15:0]reg_data;
reg start;

i2c_com u1(.clock_i2c(clock_20k),
.reset_n(reset_n),
.ack(ack),
.i2c_data(i2c_data),
.start(start),
.tr_end(tr_end),
.i2c_sclk(i2c_sclk),
.i2c_sdat(i2c_sdat));

[email protected](posedge clock_50m or negedge reset_n) //產生i2c控制時鐘-20khz
begin
if(!reset_n)
begin
clock_20k<=0;
clock_20k_cnt<=0;
end
else if(clock_20k_cnt<2499)
clock_20k_cnt<=clock_20k_cnt+1;
else
begin
clock_20k<=!clock_20k;
clock_20k_cnt<=0;
end
end

[email protected](posedge clock_20k or negedge reset_n) //配置過程控制
begin
if(!reset_n)
begin
config_step<=0;
start<=0;
reg_index<=0;
end
else
begin
if(reg_index<10)
begin
case(config_step)
0:begin
i2c_data<={8'h34,reg_data};
start<=1;
config_step<=1;
end
1:begin
if(tr_end)
begin
if(!ack)
config_step<=2;
else
config_step<=0;
start<=0;
end
end
2:begin
reg_index<=reg_index+1;
config_step<=0;
end
endcase
end
end
end
[email protected](reg_index) 
begin
case(reg_index)
0:reg_data<=16'h001f;
1:reg_data<=16'h021f;
2:reg_data<=16'h047f;
3:reg_data<=16'h067f;
4:reg_data<=16'h08f8;
5:reg_data<=16'h0a06;
6:reg_data<=16'h0c00;
7:reg_data<=16'h0e01;
8:reg_data<=16'h1002;
9:reg_data<=16'h1201;
default:reg_data<=16'h001a;
endcase
end
endmodule


關於此段程式碼的解釋:
A:此處例項化了前面提到的i2c控制的程式。
B:共對wm8731中的10個暫存器進行配置,詳細的暫存器資訊與每位資料的表示意義參考wm8731的datasheet。
C:wm8731允許的i2c控制時鐘在0-400khz有效,這裡應用20khz。
D:配置過程控制這段程式碼很關鍵,尤其要注意開始和結束訊號的控制

3.正弦波產生程式

module sinwave_gen(clock_ref,dacclk,bclk,dacdat,reset_n);
input clock_ref; //wm8731輸入時鐘,18.432Mhz;
input reset_n; 
output dacclk; 
output dacdat;
output bclk;

reg dacclk;
reg [8:0]dacclk_cnt;
reg bclk;
reg [3:0]bclk_cnt;
reg [3:0]data_num;
reg [15:0]sin_out;
reg [5:0]sin_index;

parameter CLOCK_REF=18432000;
parameter CLOCK_SAMPLE=48000;

[email protected](posedge clock_ref or negedge reset_n) //生產正弦波取樣時鐘48khz
begin
if(!reset_n)
begin
dacclk<=0;
dacclk_cnt<=0;
end
else if(dacclk_cnt>=(CLOCK_REF/(CLOCK_SAMPLE*2)-1))
begin
dacclk<=~dacclk;
dacclk_cnt<=0;
end
else
dacclk_cnt<=dacclk_cnt+1; 
end


[email protected](posedge clock_ref or negedge reset_n) //生產16位資料傳輸時鐘,正弦波取樣時鐘的32倍
begin
if(!reset_n)
begin
bclk<=0;
bclk_cnt<=0;
end
else if(bclk_cnt>=(CLOCK_REF/(CLOCK_SAMPLE*2*16*2)-1))
begin
bclk<=~bclk;
bclk_cnt<=0;
end
else
bclk_cnt<=bclk_cnt+1;
end

[email protected](negedge bclk or negedge reset_n)
begin
if(!reset_n)
data_num<=0;
else
data_num<=data_num+1;
end


[email protected](negedge dacclk or negedge reset_n)
begin
if(!reset_n)
sin_index<=0;
else if(sin_index<47)
sin_index<=sin_index+1;
else
sin_index<=0;
end

assign dacdat=sin_out[~data_num]; //產生DA轉換器數字音訊資料

[email protected](sin_index)
begin
case(sin_index)
0 : sin_out <= 0 ; //32767*sin0
1 : sin_out <= 4276 ; //32767*sin7.5(角度)
2 : sin_out <= 8480 ; //32767*sin15(角度)
3 : sin_out <= 12539 ;
4 : sin_out <= 16383 ;
5 : sin_out <= 19947 ;
6 : sin_out <= 23169 ;
7 : sin_out <= 25995 ;
8 : sin_out <= 28377 ;
9 : sin_out <= 30272 ;
10 : sin_out <= 31650 ;
11 : sin_out <= 32486 ;
12 : sin_out <= 32767 ;
13 : sin_out <= 32486 ;
14 : sin_out <= 31650 ;
15 : sin_out <= 30272 ;
16 : sin_out <= 28377 ;
17 : sin_out <= 25995 ;
18 : sin_out <= 23169 ;
19 : sin_out <= 19947 ;
20 : sin_out <= 16383 ;
21 : sin_out <= 12539 ;
22 : sin_out <= 8480 ;
23 : sin_out <= 4276 ;
24 : sin_out <= 0 ;
25 : sin_out <= 61259 ;
26 : sin_out <= 57056 ;
27 : sin_out <= 52997 ;
28 : sin_out <= 49153 ;
29 : sin_out <= 45589 ;
30 : sin_out <= 42366 ;
31 : sin_out <= 39540 ;
32 : sin_out <= 37159 ;
33 : sin_out <= 35263 ;
34 : sin_out <= 33885 ;
35 : sin_out <= 33049 ;
36 : sin_out <= 32768 ;
37 : sin_out <= 33049 ;
38 : sin_out <= 33885 ;
39 : sin_out <= 35263 ;
40 : sin_out <= 37159 ;
41 : sin_out <= 39540 ;
42 : sin_out <= 42366 ;
43 : sin_out <= 45589 ;
44 : sin_out <= 49152 ;
45 : sin_out <= 52997 ;
46 : sin_out <= 57056 ;
47 : sin_out <= 61259 ;
endcase
end
endmodule


關於此段程式碼的解釋:
由於取樣速率是48khz,在所要產生的正弦波的一個週期內進行了48此取樣,所以得到的正弦波的頻率是1khz。

4.wm8731的輸入時鐘要求為18.432Mhz,此時鐘由DE2板上27Mhz晶振由altera的PLL分頻得到。由於27Mhz不能精確地分頻到18.432Mhz,所以採用了一個能分到一個最接近的頻率,即18.409091Mhz。分頻程式如下:

module audio_pll (
inclk0,
c0);

input inclk0;
output c0;

wire [5:0] sub_wire0;
wire [0:0] sub_wire4 = 1'h0;
wire [0:0] sub_wire1 = sub_wire0[0:0];
wire c0 = sub_wire1;
wire sub_wire2 = inclk0;
wire [1:0] sub_wire3 = {sub_wire4, sub_wire2};

altpll altpll_component (
.inclk (sub_wire3),
.clk (sub_wire0),
.activeclock (),
.areset (1'b0),
.clkbad (),
.clkena ({6{1'b1}}),
.clkloss (),
.clkswitch (1'b0),
.configupdate (1'b0),
.enable0 (),
.enable1 (),
.extclk (),
.extclkena ({4{1'b1}}),
.fbin (1'b1),
.fbmimicbidir (),
.fbout (),
.locked (),
.pfdena (1'b1),
.phasecounterselect ({4{1'b1}}),
.phasedone (),
.phasestep (1'b1),
.phaseupdown (1'b1),
.pllena (1'b1),
.scanaclr (1'b0),
.scanclk (1'b0),
.scanclkena (1'b1),
.scandata (1'b0),
.scandataout (),
.scandone (),
.scanread (1'b0),
.scanwrite (1'b0),
.sclkout0 (),
.sclkout1 (),
.vcooverrange (),
.vcounderrange ());
defparam
altpll_component.clk0_divide_by = 27000000,
altpll_component.clk0_duty_cycle = 50,
altpll_component.clk0_multiply_by = 18409091,
altpll_component.clk0_phase_shift = "0",
altpll_component.compensate_clock = "CLK0",
altpll_component.inclk0_input_frequency = 37037,
altpll_component.intended_device_family = "Cyclone II",
altpll_component.lpm_hint = "CBX_MODULE_PREFIX=Audio_PLL",
altpll_component.lpm_type = "altpll",
altpll_component.operation_mode = "NORMAL",
altpll_component.port_activeclock = "PORT_UNUSED",
altpll_component.port_areset = "PORT_UNUSED",
altpll_component.port_clkbad0 = "PORT_UNUSED",
altpll_component.port_clkbad1 = "PORT_UNUSED",
altpll_component.port_clkloss = "PORT_UNUSED",
altpll_component.port_clkswitch = "PORT_UNUSED",
altpll_component.port_configupdate = "PORT_UNUSED",
altpll_component.port_fbin = "PORT_UNUSED",
altpll_component.port_inclk0 = "PORT_USED",
altpll_component.port_inclk1 = "PORT_UNUSED",
altpll_component.port_locked = "PORT_UNUSED",
altpll_component.port_pfdena = "PORT_UNUSED",
altpll_component.port_phasecounterselect = "PORT_UNUSED",
altpll_component.port_phasedone = "PORT_UNUSED",
altpll_component.port_phasestep = "PORT_UNUSED",
altpll_component.port_phaseupdown = "PORT_UNUSED",
altpll_component.port_pllena = "PORT_UNUSED",
altpll_component.port_scanaclr = "PORT_UNUSED",
altpll_component.port_scanclk = "PORT_UNUSED",
altpll_component.port_scanclkena = "PORT_UNUSED",
altpll_component.port_scandata = "PORT_UNUSED",
altpll_component.port_scandataout = "PORT_UNUSED",
altpll_component.port_scandone = "PORT_UNUSED",
altpll_component.port_scanread = "PORT_UNUSED",
altpll_component.port_scanwrite = "PORT_UNUSED",
altpll_component.port_clk0 = "PORT_USED",
altpll_component.port_clk1 = "PORT_UNUSED",
altpll_component.port_clk2 = "PORT_UNUSED",
altpll_component.port_clk3 = "PORT_UNUSED",
altpll_component.port_clk4 = "PORT_UNUSED",
altpll_component.port_clk5 = "PORT_UNUSED",
altpll_component.port_clkena0 = "PORT_UNUSED",
altpll_component.port_clkena1 = "PORT_UNUSED",
altpll_component.port_clkena2 = "PORT_UNUSED",
altpll_component.port_clkena3 = "PORT_UNUSED",
altpll_component.port_clkena4 = "PORT_UNUSED",
altpll_component.port_clkena5 = "PORT_UNUSED",
altpll_component.port_extclk0 = "PORT_UNUSED",
altpll_component.port_extclk1 = "PORT_UNUSED",
altpll_component.port_extclk2 = "PORT_UNUSED",
altpll_component.port_extclk3 = "PORT_UNUSED";


endmodule



5.加一上電覆位程式,如下:

module reset_delay(clock_50m,rst_n); //復位延時65536*20ns
input clock_50m;
output rst_n;
reg [15:0]cnt;
reg rst_n;

[email protected](posedge clock_50m)
begin
if(cnt<16'hffff)
begin
cnt<=cnt+1;
rst_n<=0;
end
else
rst_n<=1; 
end
endmodule



最後加一top模組。將上述程式碼例項化到top模組中,即可使用了。