DE2实践之WM8731产生正弦波
2013-11-23 14:57
141 查看
经历近三周的时间,终于搞定了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写控制代码)
这里需要关键注意的是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中寄存器的配置程序
关于此段代码的解释:
A:此处实例化了前面提到的i2c控制的程序。
B:共对wm8731中的10个寄存器进行配置,详细的寄存器信息与每位数据的表示意义参考wm8731的datasheet。
C:wm8731允许的i2c控制时钟在0-400khz有效,这里应用20khz。
D:配置过程控制这段代码很关键,尤其要注意开始和结束信号的控制
3.正弦波产生程序
关于此段代码的解释:
由于采样速率是48khz,在所要产生的正弦波的一个周期内进行了48此采样,所以得到的正弦波的频率是1khz。
4.wm8731的输入时钟要求为18.432Mhz,此时钟由DE2板上27Mhz晶振由altera的PLL分频得到。由于27Mhz不能精确地分频到18.432Mhz,所以采用了一个能分到一个最接近的频率,即18.409091Mhz。分频程序如下:
5.加一上电复位程序,如下:
最后加一top模块。将上述代码实例化到top模块中,即可使用了。
一.本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模块中,即可使用了。
相关文章推荐
- 利用DE2上的WM8731D/A转换器产生正弦波
- 关于连续使用fork()到底产生多少个子进程问题的实践
- 怎样用单片机和DA转换产生一个正弦波.
- <<敏捷软件开发:原则、模式与实践>>时,素数产生程序,第一个java
- DE2-115开发板学习(1_时钟信号引脚分配与复位信号的产生)
- 【Linux网络编程笔记】TCP短连接产生大量TIME_WAIT导致无法对外建立新TCP连接的原因及解决方法—实践篇
- 【Linux网络编程笔记】TCP短连接产生大量TIME_WAIT导致无法对外建立新TCP连接的原因及解决方法—实践篇
- 【Linux网络编程笔记】TCP短连接产生大量TIME_WAIT导致无法对外建立新TCP连接的原因及解决方法—实践篇
- TCP短连接产生大量TIME_WAIT导致无法对外建立新TCP连接的原因及解决方法—实践篇
- 用AVR单片机来产生正弦波信号
- [TM4C123单片机实践] 配置SSI并驱动DAC7811显示正弦波
- 单片机 利用C语言产生正弦波DA数据
- Proteus之51MCU学习之路--如何用PWM波产生正弦波?
- 团队基础生成自动化流程之最佳实践总论(III) – 重写生成号产生机制
- spring-oauth-server实践:授权方式四:client_credentials 模式下access_token的产生
- Audio Codec介绍-4(利用Adobe Audition工具来产生正弦波)
- 机器学习笔记(十二)朴素贝叶斯算法及实践(NB算法的产生及参数估计)
- Audio Codec介绍-4(利用Adobe Audition工具来产生正弦波)
- 【Linux网络编程笔记】TCP短连接产生大量TIME_WAIT导致无法对外建立新TCP连接的原因及解决方法—实践篇
- java 产生正弦波