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;


always@(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
always@(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));

always@(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

always@(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
always@(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;

always@(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


always@(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

always@(negedge bclk or negedge reset_n)
begin
if(!reset_n)
data_num<=0;
else
data_num<=data_num+1;
end


always@(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转换器数字音频数据

always@(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;

always@(posedge clock_50m)
begin
if(cnt<16'hffff)
begin
cnt<=cnt+1;
rst_n<=0;
end
else
rst_n<=1; 
end
endmodule



最后加一top模块。将上述代码实例化到top模块中,即可使用了。



  • 9
    点赞
  • 27
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值