Experimental Manuals FII-PRA040 FPGA Board Based FPGA Tutor Risc-V

Audio 8978 Loopback Experiment (WM8978 Audio Sub Development Board) , How I2S (Inter-IC Sound) bus work ? – – FII-PRA040 Altera Risc-V Tutorial Experiment 16

Experiment 16 8978 Audio Loopback Experiment

16.1 Experiment Objective

  1. Learn about I2S (Inter-IC Sound) bus and how it works
  2. Familiar with the working mode of WM8978. And by configuring the interface mode and selecting the relevant registers in combination with the development board, complete the data transmission and reception, and verify it

16.2 Experiment Implement

  1. Perform audio loopback test by configuring the onboard audio chip WM8978 to check if the hardware is working properly
  2. Adjust the volume output level with the keys.

16.3 Experiment

16.3.1 WM8978 Introduction

WM8978 is a low power, high quality stereo multimedia digital signal CODEC introduced by Wolfson. It is mainly used in portable applications such as digital cameras and camcorders. Advanced on-chip digital signal processing includes a 5-band equaliser, a mixed signal Automatic Level Control for the microphone or line input through the ADC as well as a purely digital limiter function for record or playback. Additional digital filtering options are available in the ADC path, to cater for application filtering, such as “wind noise reduction”.

See Figure 16.1 for the internal structure block diagram of WM8978.

截取-1

Figure 16.1 WM8978 internal structure block diagram

Figure 16.2 Schematics of the audio part of the development board

16.3.2 WM8978 Control Interface Timing

The WM8978 control interface has two-wire mode and three-wire mode. The specific mode is selected by the MODE pin connection of WM8978. When the mode pin is connected to a low voltage level, it is a two-wire mode, and when it is connected to a high voltage level, it is a three-wire mode. The development board mode pin is grounded. When the control interface is in two-wire mode, the timing diagram is shown in Figure 16.3. The timing diagram is the same as the IIC timing. The device address of WM8978 is fixed to 7’b0011010. This chip register only supports writing and does not support reading.

截取-3.jpg

Figure 16.3 Timing diagram of the two-wire mode interface

16.3.3 I2S Audio Bus Protocol

I2S (Inter-IC Sound Bus) is just a branch of PCM, the interface definition is the same, I2S sampling frequency is generally 44.1KHz and 48KHz, PCM sampling frequency is generally 8K, 16K. There are four groups of signals: bit clock signal, synchInter-IC Sound Busronization signal, data input, data output.

I2S is a bus standard developed by Philips for audio data transmission between digital audio devices. In the Philips I2S standard, both the hardware interface specification and the format of digital audio data are specified. I2S has three main signals: the serial clock SCLK, also known as the bit clock BCLK, which corresponds to each bit of data of digital audio. The frequency of SCLK = 2 × sampling frequency × sampling number of bits. The frame clock LRCK is used to switch the data of the left and right channels. An LRCK of “0” indicates that data of the left channel is being transmitted, and “1” indicates that data of the right channel is being transmitted. LRCLK == FS, is the sampling frequency serial data SDATA, which is audio data expressed in two’s complement. Sometimes in order to enable better synchronization between systems, another signal MCLK is needed, which is called the master clock, or also called the system Clock (System Clock). It is 256 or 384 times the sampling frequency.

The timing of the I2S protocol is shown in Figure 16.4. However many bits of data the I2S format signal has, the most significant bit of the data always appears at the second BCLK pulse after the LRCK change (that is, the beginning of a frame). This allows the number of significant digits at the receiving end and the transmitting end to be different. If the receiving end can process less significant bits than the transmitting end, the extra low-order data in the data frame can be discarded; if the receiving end can process more significant bits than the transmitting end, it can make up the remaining bits by itself. This synchronization mechanism makes the interconnection of digital audio equipment more convenient without causing data errors.

Figure 16.4 I2S timing protocol

16.3.4 Main Program Design

  1. WM8978 register configuration program

Only the program of register configuration program is given here, please refer to the project file for the complete program

module wm8978_config

(

input clk_50m,

output reg cfg_done=0,

input rst_n,

input rxd,

output txd,

input key1,

input key2,

output i2c_sclk,

inout i2c_sdat

);

wire tr_end;

reg [4:0] i;

//************************************

reg [23:0] i2c_data_r=0;

wire [7:0] data_read ;

reg [7:0] read_req ;

reg uart_rd =0;

reg uart_wr =0;

reg [7:0] txd_i2c_data;

reg txd_start = 0;

wire txd_busy;

wire [7:0] rxd_i2c_data;

wire rxd_ready;

wire rxd_eop;

wire test_pin;

uart_transceiver uart_transceiver_inst

(

.sys_clk (clk_50m), // 50m

.uart_rx (rxd),

.uart_tx (txd),

.divisor (55), // 115200 * 8

.rx_data (rxd_i2c_data),

.rx_done (rxd_ready),

.rx_eop (rxd_eop),

.tx_data (txd_i2c_data),

.tx_wr (txd_start),

.tx_done (),

.tx_busy (txd_busy),

.test_pin (),

.sys_rst ()

);

reg rx_end_ack = 0;

reg rx_end = 0;

always @ (posedge clk_50m)

if(rxd_eop) rx_end <= 1;

else if(rx_end_ack) rx_end <= 0;

reg [7:0] cmd_dir = 0;

reg [3:0] uart_st = 0;

always @ (posedge clk_50m)

if(cfg_done == 0)

begin

rx_end_ack <= 0;

uart_wr <= 0;

uart_rd <= 0;

uart_st <= 0;

end

else case(uart_st)

0:

begin

rx_end_ack <= 0;

uart_wr <= 0;

uart_rd <= 0;

if(rxd_ready)

begin

cmd_dir <= rxd_i2c_data;

uart_st <= 1;

end

end

1:

begin

if(rxd_ready)

begin

i2c_data_r[23:16] <= rxd_i2c_data;

uart_st <= 2;

end

end

2:

begin

if(rxd_ready)

begin

i2c_data_r[15:08] <= rxd_i2c_data;

if(cmd_dir[0]) uart_st <= 5;

else uart_st <= 3;

end

end

3: // write

begin

if(rxd_ready)

begin

i2c_data_r[07:00] <= rxd_i2c_data;

uart_wr <= 1;

uart_st <= 4;

end

end

4:

begin

if(tr_end)

begin

uart_wr <= 0;

uart_st <= 7;

end

end

5: //read

begin

uart_rd <= 1;

uart_st <= 6;

end

6:

begin

uart_rd <= 0;

if(tr_end)

begin

txd_i2c_data <= data_read;

txd_start <= 1;

uart_st <= 7;

end

end

7:

begin

txd_start <= 0;

if(rx_end)

begin

rx_end_ack <= 1;

uart_st <= 0;

end

else rx_end_ack <= 0;

end

endcase

//***************************************

reg start;

//parameter define

reg [5:0] PHONE_VOLUME = 6’d32;

reg [5:0] SPEAK_VOLUME = 6’d32;

reg [31:0] i2c_data=0 ;

reg [7:0] start_init_cnt;

reg [4:0] init_reg_cnt ;

reg [25:0] on_counter;

reg [25:0] off_counter;

reg key_up, key_down;

always @(posedge clk_50m , negedge cfg_done)

if (!cfg_done) begin

on_counter<=0;

off_counter<=0;

key_up<=1’b0;

key_down<=1’b0;

end

else begin

if (key1==1’b1)

on_counter<=0;

else if ((key1==1’b0)& (on_counter<=500000))

on_counter<=on_counter+1’b1;

if (on_counter==49950)

key_up<=1’b1;

else

key_up<=1’b0;

if (key2==1’b1)

off_counter<=0;

else if ((key2==1’b0)& (off_counter<=500000))

off_counter<=off_counter+1’b1;

if (off_counter==49950)

key_down<=1’b1;

else

key_down<=1’b0;

end

always @(posedge clk_50m , negedge cfg_done)

if (!cfg_done) begin

PHONE_VOLUME <=6’d32 ;

end

else begin

if (( 2<=PHONE_VOLUME )&( PHONE_VOLUME <=56)&(on_counter==49948))

PHONE_VOLUME <=PHONE_VOLUME+6 ;

else if (( 8<=SPEAK_VOLUME )&( SPEAK_VOLUME <=62)& (off_counter==49948))

PHONE_VOLUME <=PHONE_VOLUME-6 ;

else PHONE_VOLUME <=PHONE_VOLUME ;

end

always @ ( posedge clk_50m )

if( rst_n==1’b0 ) begin

i <= 5’d0;

read_req<=0 ;

i2c_data <= 32’h000000;

start <= 1’b0;

cfg_done <=0;

end

else begin

case( i )

0: begin

if( tr_end ) begin start <= 1’b00; i <= i + 1’b1; end

else begin start <= 1’b1; i2c_data <= {7’h1a,1’b0,8’h00,7’d0 ,9’b1}; end

end

1: begin

if( tr_end ) begin start <= 1’b0; i <= i + 1’b1; end

else begin start <= 1’b1; i2c_data <={7’h1a,1’b0, 8’h00,7’d1 ,9’b1_0010_1111}; end

end

2: begin

if( tr_end ) begin start <= 1’b0; i <= i + 1’b1; end

else begin start <= 1’b1; i2c_data <= {7’h1a,1’b0,8’h00,7’d2 ,9’b1_1011_0011}; end

end

3: begin

if( tr_end ) begin start <= 1’b0; i <= i + 1’b1; end

else begin start <= 1’b1; i2c_data <= {7’h1a,1’b0,8’h00,7’d3 ,9’b0_0110_1111}; end

end

4: begin

if( tr_end ) begin start <= 1’b0; i <= i + 1’b1; end

else begin start <= 1’b1; i2c_data <= {7’h1a,1’b0,8’h00,7’d4 ,{2’d0,2’b11,5’b10000}}; end

end

5: begin

if( tr_end ) begin start <= 1’b0; i <= i + 1’b1; end

else begin start <= 1’b1; i2c_data <={7’h1a,1’b0,8’h00,7’d6 ,9’b0_0000_0001}; end

end

6: begin

if( tr_end ) begin start <= 2’b00; i <= i + 1’b1; end

else begin start <= 1’b1; i2c_data<= {7’h1a,1’b0,8’h00,7’d7 ,9’b0_0000_0001}; end

end

7: begin

if( tr_end ) begin start <= 1’b0; i <= i + 1’b1; end

else begin start <= 1’b1; i2c_data <= {7’h1a,1’b0,8’h00,7’d10,9’b0_0000_1000}; end

end

8: begin

if( tr_end ) begin start <= 1’b0; i <= i + 1’b1; end

else begin start <= 1’b1; i2c_data <= {7’h1a,1’b0,8’h00,7’d14,9’b1_0000_1000}; end

end

9: begin

if( tr_end ) begin start <= 1’b0; i <= i + 1’b1; end

else begin start <= 1’b1; i2c_data <= {7’h1a,1’b0,8’h00,7’d43,9’b0_0001_0000}; end

end

10: begin

if( tr_end ) begin start <= 1’b0; i <= i + 1’b1; end

else begin start <= 1’b1; i2c_data <={7’h1a,1’b0,8’h00,7’d47,9’b0_0111_0000}; end

end

11: begin

if( tr_end ) begin start <= 1’b0; i <= i + 1’b1; end

else begin start <= 1’b1; i2c_data <={7’h1a,1’b0,8’h00,7’d48,9’b0_0111_0000}; end

end

12: begin

if( tr_end ) begin start <= 1’b0; i <= i + 1’b1; end

else begin start <= 1’b1; i2c_data <={7’h1a,1’b0,8’h00,7’d49,9’b0_0000_0110}; end

end

13: begin

if( tr_end ) begin start <= 1’b0; i <= i + 1’b1; end

else begin start <= 1’b1; i2c_data <= {7’h1a,1’b0,8’h00,7’d50,9’b1 };end

end

14: begin

if( tr_end ) begin start <= 1’b0; i <= i + 1’b1; end

else begin start <= 1’b1; i2c_data <= {7’h1a,1’b0,8’h00,7’d51,9’b1 };end

end

15: begin

if( tr_end ) begin start <= 1’b0; i <= i + 1’b1; end

else begin start <= 1’b1; i2c_data <={7’h1a,1’b0,8’h00,7’d52,{3’b010,PHONE_VOLUME}};end

end

16: begin

if( tr_end ) begin start <= 1’b0; i <= i + 1; end

else begin start <= 1’b1; i2c_data <= {7’h1a,1’b0,8’h00,7’d53,{3’b110,PHONE_VOLUME}};end

end

17: begin

cfg_done<=1 ;

if (uart_wr)

begin

start <= 1’b1;

i2c_data <={cmd_dir,i2c_data_r} ;

i<=i+1 ;

end

if (uart_rd)

begin

read_req <= 1’b1;

i2c_data <={cmd_dir,i2c_data_r} ;

i<=i+2 ;

end

if (key_up|key_down)

i<= 15;

end

18: begin

if( tr_end ) begin start <= 1’b0; i <=19; end

else i<=20;

end

19: begin

if( tr_end ) begin read_req <= 1’b0; i <= 19; end

else begin i<=21;end

end

default:i<=1 ;

endcase

end

i2c_control i2c_control_inst (

.Clk (clk_50m),

.Rst_n(rst_n),

.wrreg_req(start) ,

.rdreg_req (read_req),

.addr({i2c_data[15:8],i2c_data[23:16]}) , //16bit

.addr_mode(0),

.wrdata (i2c_data[7:0]), //

.rddata (data_read), //8bit

.device_id (i2c_data[31:24]), //8bit

.RW_Done(tr_end),

.ack (),

.i2c_sclk(i2c_sclk),

.i2c_sdat(i2c_sdat)

);

endmodule

  1. Audio signal acquisition program
module audio_receive (

//system clock 50MHz

input rst_n ,

//wm8978 interface

input aud_bclk ,

input aud_lrc ,

input aud_adcdat,

//user interface

output reg rx_done ,

output reg [31:0] adc_data

);

parameter WL = 6’d32 ;

reg aud_lrc_d0;

reg [ 5:0] rx_cnt;

reg [31:0] adc_data_t;

wire lrc_edge ;

assign lrc_edge = aud_lrc ^ aud_lrc_d0;

always @(posedge aud_bclk or negedge rst_n) begin

if(!rst_n)

aud_lrc_d0 <= 1’b0;

else

aud_lrc_d0 <= aud_lrc;

end

always @(posedge aud_bclk or negedge rst_n) begin

if(!rst_n) begin

rx_cnt <= 6’d0;

end

else if(lrc_edge == 1’b1)

rx_cnt <= 6’d0;

else if(rx_cnt < 6’d35)

rx_cnt <= rx_cnt + 1’b1;

end

always @(posedge aud_bclk or negedge rst_n) begin

if(!rst_n) begin

adc_data_t <= 32’b0;

end

else if(rx_cnt < WL)

adc_data_t[WL – 1’d1 – rx_cnt] <= aud_adcdat;

always @(posedge aud_bclk or negedge rst_n) begin

if(!rst_n) begin

rx_done <= 1’b0;

adc_data <= 32’b0;

end

else if(rx_cnt == 6’d32) begin

rx_done <= 1’b1;

adc_data<= adc_data_t;

end

else

rx_done <= 1’b0;

end

endmodule

  1. Audio sending module
module audio_send (

input rst_n ,

input aud_bclk ,

input aud_lrc ,

output reg aud_dacdat,

input [31:0] dac_data ,

output reg tx_done

);

parameter WL = 6’d32 ;

reg aud_lrc_d0;

reg [ 5:0] tx_cnt;

reg [31:0] dac_data_t;

wire lrc_edge;

assign lrc_edge = aud_lrc ^ aud_lrc_d0;

always @(posedge aud_bclk or negedge rst_n) begin

if(!rst_n)

aud_lrc_d0 <= 1’b0;

else

aud_lrc_d0 <= aud_lrc;

end

always @(posedge aud_bclk or negedge rst_n) begin

if(!rst_n) begin

tx_cnt <= 6’d0;

dac_data_t <= 32’d0;

end

else if(lrc_edge == 1’b1) begin

tx_cnt <= 6’d0;

dac_data_t <= dac_data;

end

else if(tx_cnt < 6’d35)

tx_cnt <= tx_cnt + 1’b1;

end

always @(posedge aud_bclk or negedge rst_n) begin

if(!rst_n) begin

tx_done <= 1’b0;

end

else if(tx_cnt == 6’d32)

tx_done <= 1’b1;

else

tx_done <= 1’b0;

end

always @(negedge aud_bclk or negedge rst_n) begin

if(!rst_n) begin

aud_dacdat <= 1’b0;

end

else if(tx_cnt < WL)

aud_dacdat <= dac_data_t[WL – 1’d1 – tx_cnt];

else

aud_dacdat <= 1’b0;

end

endmodule

  1. Main program
module audio_test(

input wire sys_clk_50,

input wire rst_n,

input rxd,

output txd,

output [7:0] led ,

input key1,

input key2,

inout wm_sdin,

output wm_sclk,

input wire wm_lrc,

input wire wm_bclk,

input wire adcdat,

output wire dacdat,

output wire mclk

);

wire cfg_done ;

assign led ={7’h7f,~cfg_done} ;

pll_50_12 pll_50_12_inst

(

// Clock out ports

.c0(clk_out_12), // output clk_out_12

// Status and control signals

.areset(~rst_n), // input reset

.locked(locked), // output locked

// Clock in ports

.inclk0(sys_clk_50)); // input sys_clk_50

wire clk_out_12 ;

assign mclk = clk_out_12 ;

wm8978_config wm8978_config_inst

(

.key1 (key1),

.key2 (key2),

.clk_50m (sys_clk_50 ) ,

.rst_n (rst_n) ,

.cfg_done (cfg_done) ,

.i2c_sclk (wm_sclk) ,

.rxd (rxd),

.txd (txd),

.i2c_sdat (wm_sdin)

);

wire [31:0] adc_data ;

audio_receive

audio_receive_inst(

.rst_n (rst_n),

.aud_bclk (wm_bclk),

.aud_lrc (wm_lrc),

.aud_adcdat (adcdat),

.adc_data (adc_data),

.rx_done (rx_done)

);

audio_send audio_send_inst(

.rst_n (rst_n),

.aud_bclk (wm_bclk),

.aud_lrc (wm_lrc),

.aud_dacdat (dacdat),

.dac_data (adc_data),

.tx_done (tx_done)

);

endmodule

16.4 Experiment Verification

  1. Pin assignment

Table 16.1 Pin assignment

Signal Name Port Description Network Label FPGA Pin
Sys_clk_50 System 50M clock C10_50MCLK G21
Reset_n System reset signal KEY1 Y4
Wm_sdin 8978 register configuration data line I2C_SDA C13
Wm_sclk 8978 register configuration clock I2C_SCL D13
Wm_lrc 8978 align clock WM_LRCK AB19
Wm_bclk 8978 bit clock WM_BCLK AA19
adcdat ADC input of 8978 WM_MISO AA18
Dacdat DAC input of 8978 WM_MOSI Y17
Mack PLL provides 8978 working master clock WM_MCLK W17
Key1 Volume up button Key2 V5
Key2 Volume down button Key7 AB3
txd Serial transmit TTL_RX E16
rxd Serial receive TTL_TX F15
  1. Board verification


As shown in Figure 16.5 below, after the FPGA development board is programmed, use a dual male audio cable, with one end plugged into the red audio receiver end and the other end plugged into a music player. Plug the headphone into the green audio playback port. The music can be heard from player. The volume is divided into 5 gears. Press the UP key to increase the volume and press the down key to decrease the volume.


Figure 16.5 wm8978 board verification

Related posts