0.AI工具:WorkBuddy,小U同学(DeepIn);硬件:小月电子安路FPGA开发板(EG4A20BG256),WCMCU-1334模块(UDA1334);IDE:安路TD_2026.1_SP2。
1.DTMF信号频率:
2.频率选择:
I2S设备UDA1334,最高支持100K采样频率,24位数据,在实际中,有以下选择:
| 采样频率(KHz) | 数据(bit) | bclk频率(MHz) |
| 8 | 16 | 0.256 |
| 16 | 16 | 0.512 |
| 44.1 | 16 | 1.4112 |
| 48 | 16 | 1.536 |
| 48 | 24 | 2.304 |
| 48 | 32 | 3.072 |
| 96 | 24 | 4.608 |
| 96 | 32 | 6.144 |
考虑到时隙,取3.072M作为bclk,48K采样,24位数据。另,UDA1334默认SF0=0,SF1=1,为I2S格式;MUTE=0,没有静音;DEEM=0,关去加重。
3.采用比较简单的方法实现驱动UDA1334,生成一个480Hz的方波信号:
//============================================================================= // i2s_uda1334.v -- 最简 I2S 发送器(UDA1334A),输出 ~480 Hz 方波单音 //----------------------------------------------------------------------------- // 连接: // bclk <- PLL产生3.072 MHz 位时钟(接 UDA1334A 的 SCK/BCLK) // wsel -> LRCK / WS // din -> DIN // rst_n -> 复位,低有效 // UDA1334A: SF0=0 / SF1=0 选 I2S 格式,MUTE 解除,PWR 使能 // // 时钟关系: // bclk = 3.072 MHz,一帧 64 个 bclk(左 32 bit + 右 32 bit) // wsel = bclk / 64 = 48 kHz 采样率 // 每 50 个采样翻转一次电平 -> 48000 / (50*2) = 480 Hz 方波 // // I2S 时序: // wsel 在 bclk 下降沿翻转,低 = 左声道,高 = 右声道 // din 在 bclk 下降沿更新,DAC 在上升沿采样,MSB first // wsel 翻转后空 1 个 bclk 再送 MSB(I2S 规范要求的 1 bit 延迟) // 24 bit 有效数据左对齐放在 32 bit 时隙的高位 //============================================================================= `timescale 1ns / 1ps module i2s_uda1334 ( input clk, // 50MHz input rst_n, // 异步复位,低有效 output wire bclk, output reg wsel, // 声道选择 / LRCK output reg din // 串行数据 ); // 24bit 有符号幅度:+0x400000 / -0x400000 localparam signed [23:0] AMP_P = 24'h400000; // localparam signed [23:0] AMP_N = 24'hC00000; // localparam [5:0] HALF = 6'd50; // 半周期采样数:48 kHz/50/2 = 480 Hz reg [5:0] bcnt; // 帧内位序号 0~63 reg [5:0] hcnt; // 半周期采样计数 0~49 reg pol; // 方波极性:0 = 负半周,1 = 正半周 reg [23:0] samp; // 当前样点(左右声道相同 = 单声道) reg [31:0] sr; // 并串移位寄存器 PLL_1 PLL1( .refclk(clk), .reset(~rst_n), .clk0_out(bclk) //3.072M ); always @(negedge bclk or negedge rst_n) begin if (!rst_n) begin bcnt <= 6'd0; hcnt <= 6'd0; pol <= 1'b0; samp <= 0; sr <= 32'd0; wsel <= 1'b0; din <= 1'b0; end else begin // ---- 帧内位计数 ---- bcnt <= (bcnt == 6'd63) ? 6'd0 : bcnt + 6'd1; // ---- 每帧(每 64 个 bclk)出一个样点,每 HALF 个样点翻一次极性 ---- if (bcnt == 6'd63) begin if (hcnt >= HALF - 6'd1) begin hcnt <= 6'd0; pol <= ~pol; samp <= pol ? AMP_N : AMP_P; // 翻转后的电平 end else begin hcnt <= hcnt + 6'd1; end end // ---- 并串转换 + wsel 产生 ---- if (bcnt == 6'd63) begin // 左声道起点 wsel <= 1'b0; din <= 1'b0; // 1 bit 延迟槽 sr <= {samp, 8'h00}; end else if (bcnt == 6'd31) begin // 右声道起点 wsel <= 1'b1; din <= 1'b0; // 1 bit 延迟槽 sr <= {samp, 8'h00}; end else begin // 31 个数据位,MSB first din <= sr[31]; sr <= {sr[30:0], 1'b0}; end end end endmodule其sdc约束:
#--------------------------------------------------------------- # 1) 输入参考时钟 50 MHz(周期 20 ns) #--------------------------------------------------------------- create_clock -name clk -period 20.000 [get_ports {clk}] #--------------------------------------------------------------- # 2) 自动推导 PLL 输出时钟(bclk = 3.072 MHz) #--------------------------------------------------------------- derive_clocks以下是“声音频率器”APP获得的频谱,可以看出对于方波信号来说,其频谱十分丰富的。
4.输出480Hz正弦信号,24位数据:
//============================================================================= // i2s_uda1334.v -- I2S 发送器(UDA1334A),输出 480 Hz 正弦测试音 //----------------------------------------------------------------------------- // bclk = 3.072 MHz(PLL 直接产生),一帧 64 个 bclk(左 32 + 右 32) // LRCK = bclk/64 = 48 kHz // 正弦表 100 点/周期 -> 48000/100 = 480 Hz(整数周期,无泄漏/无抖动) // // 帧结构(每声道 32 个时隙):1 bit 延迟槽 + 24 bit 数据(MSB first) + 7 bit 补 0 // din 在 bclk 下降沿更新,DAC 在上升沿采样 // // PLL(F=50MHz):M=29, N=4, C=118 -> VCO=362.5MHz, fout=3.072034MHz (+0.0011%) // 实际 LRCK = 48000.5 Hz,故正弦实际频率 = 480.005 Hz(+11 ppm,可忽略) //============================================================================= `timescale 1ns / 1ps module i2s_uda1334 ( input clk, // 50 MHz input rst_n, // 异步复位,低有效 output wire bclk, // 3.072 MHz 位时钟 output reg wsel, // LRCK / WS,0 = 左声道 output reg din // 串行数据 ); // ---------------- 参数 ---------------- localparam NSAMP = 100; // 每周期样点数 -> 480 Hz localparam [23:0] AMP = 24'h400000; // 峰值 0x400000 = 0.5 FS = -6 dBFS // ---------------- 正弦查找表(100 点)---------------- function [23:0] sin_lut; input [6:0] a; // 0 ~ 99 begin case (a) 7'd 0 : sin_lut = 24'sh000000; 7'd 1 : sin_lut = 24'sh0404C3; 7'd 2 : sin_lut = 24'sh080576; 7'd 3 : sin_lut = 24'sh0BFE0E; 7'd 4 : sin_lut = 24'sh0FEA89; 7'd 5 : sin_lut = 24'sh13C6EF; 7'd 6 : sin_lut = 24'sh178F5A; 7'd 7 : sin_lut = 24'sh1B3FF8; 7'd 8 : sin_lut = 24'sh1ED50D; 7'd 9 : sin_lut = 24'sh224AFC; 7'd10 : sin_lut = 24'sh259E46; 7'd11 : sin_lut = 24'sh28CB8E; 7'd12 : sin_lut = 24'sh2BCF9F; 7'd13 : sin_lut = 24'sh2EA76C; 7'd14 : sin_lut = 24'sh315017; 7'd15 : sin_lut = 24'sh33C6EF; 7'd16 : sin_lut = 24'sh360978; 7'd17 : sin_lut = 24'sh381569; 7'd18 : sin_lut = 24'sh39E8B0; 7'd19 : sin_lut = 24'sh3B8175; 7'd20 : sin_lut = 24'sh3CDE1C; 7'd21 : sin_lut = 24'sh3DFD44; 7'd22 : sin_lut = 24'sh3EDDCB; 7'd23 : sin_lut = 24'sh3F7ECF; 7'd24 : sin_lut = 24'sh3FDFAB; 7'd25 : sin_lut = 24'sh400000; 7'd26 : sin_lut = 24'sh3FDFAB; 7'd27 : sin_lut = 24'sh3F7ECF; 7'd28 : sin_lut = 24'sh3EDDCB; 7'd29 : sin_lut = 24'sh3DFD44; 7'd30 : sin_lut = 24'sh3CDE1C; 7'd31 : sin_lut = 24'sh3B8175; 7'd32 : sin_lut = 24'sh39E8B0; 7'd33 : sin_lut = 24'sh381569; 7'd34 : sin_lut = 24'sh360978; 7'd35 : sin_lut = 24'sh33C6EF; 7'd36 : sin_lut = 24'sh315017; 7'd37 : sin_lut = 24'sh2EA76C; 7'd38 : sin_lut = 24'sh2BCF9F; 7'd39 : sin_lut = 24'sh28CB8E; 7'd40 : sin_lut = 24'sh259E46; 7'd41 : sin_lut = 24'sh224AFC; 7'd42 : sin_lut = 24'sh1ED50D; 7'd43 : sin_lut = 24'sh1B3FF8; 7'd44 : sin_lut = 24'sh178F5A; 7'd45 : sin_lut = 24'sh13C6EF; 7'd46 : sin_lut = 24'sh0FEA89; 7'd47 : sin_lut = 24'sh0BFE0E; 7'd48 : sin_lut = 24'sh080576; 7'd49 : sin_lut = 24'sh0404C3; 7'd50 : sin_lut = 24'sh000000; 7'd51 : sin_lut = 24'shFBFB3D; 7'd52 : sin_lut = 24'shF7FA8A; 7'd53 : sin_lut = 24'shF401F2; 7'd54 : sin_lut = 24'shF01577; 7'd55 : sin_lut = 24'shEC3911; 7'd56 : sin_lut = 24'shE870A6; 7'd57 : sin_lut = 24'shE4C008; 7'd58 : sin_lut = 24'shE12AF3; 7'd59 : sin_lut = 24'shDDB504; 7'd60 : sin_lut = 24'shDA61BA; 7'd61 : sin_lut = 24'shD73472; 7'd62 : sin_lut = 24'shD43061; 7'd63 : sin_lut = 24'shD15894; 7'd64 : sin_lut = 24'shCEAFE9; 7'd65 : sin_lut = 24'shCC3911; 7'd66 : sin_lut = 24'shC9F688; 7'd67 : sin_lut = 24'shC7EA97; 7'd68 : sin_lut = 24'shC61750; 7'd69 : sin_lut = 24'shC47E8B; 7'd70 : sin_lut = 24'shC321E4; 7'd71 : sin_lut = 24'shC202BC; 7'd72 : sin_lut = 24'shC12235; 7'd73 : sin_lut = 24'shC08131; 7'd74 : sin_lut = 24'shC02055; 7'd75 : sin_lut = 24'shC00000; 7'd76 : sin_lut = 24'shC02055; 7'd77 : sin_lut = 24'shC08131; 7'd78 : sin_lut = 24'shC12235; 7'd79 : sin_lut = 24'shC202BC; 7'd80 : sin_lut = 24'shC321E4; 7'd81 : sin_lut = 24'shC47E8B; 7'd82 : sin_lut = 24'shC61750; 7'd83 : sin_lut = 24'shC7EA97; 7'd84 : sin_lut = 24'shC9F688; 7'd85 : sin_lut = 24'shCC3911; 7'd86 : sin_lut = 24'shCEAFE9; 7'd87 : sin_lut = 24'shD15894; 7'd88 : sin_lut = 24'shD43061; 7'd89 : sin_lut = 24'shD73472; 7'd90 : sin_lut = 24'shDA61BA; 7'd91 : sin_lut = 24'shDDB504; 7'd92 : sin_lut = 24'shE12AF3; 7'd93 : sin_lut = 24'shE4C008; 7'd94 : sin_lut = 24'shE870A6; 7'd95 : sin_lut = 24'shEC3911; 7'd96 : sin_lut = 24'shF01577; 7'd97 : sin_lut = 24'shF401F2; 7'd98 : sin_lut = 24'shF7FA8A; 7'd99 : sin_lut = 24'shFBFB3D; default: sin_lut = 24'h000000; endcase end endfunction // ---------------- 内部信号 ---------------- reg [5:0] bcnt; // 帧内 bclk 序号 0~63 reg [6:0] ph; // 正弦表相位 0~99 reg [23:0] samp; // 当前样点(左右声道共用) reg [31:0] sr; // 并串移位寄存器(24 bit 数据左对齐) wire [23:0] sin_val = sin_lut(ph); // ---------------- PLL ---------------- PLL_1 PLL1 ( .refclk (clk), .reset (~rst_n), .clk0_out(bclk) ); // ---------------- I2S 帧生成 ---------------- always @(negedge bclk or negedge rst_n) begin if (!rst_n) begin bcnt <= 6'd0; ph <= 7'd0; samp <= 24'd0; // 静音起步,避免上电爆音 sr <= 32'd0; wsel <= 1'b0; din <= 1'b0; end else begin bcnt <= (bcnt == 6'd63) ? 6'd0 : bcnt + 6'd1; if (bcnt == 6'd63) begin // ---- 左声道起点(LRCK 变低)---- wsel <= 1'b0; din <= 1'b0; // I2S 1 bit 延迟槽 samp <= sin_val; // 锁存新样点 sr <= {sin_val, 8'h00}; // 24 bit 左对齐 ph <= (ph == NSAMP-1) ? 7'd0 : ph + 7'd1; end else if (bcnt == 6'd31) begin // ---- 右声道起点(LRCK 变高)---- wsel <= 1'b1; din <= 1'b0; // 1 bit 延迟槽 sr <= {samp, 8'h00}; // 左右同一份数据(单声道) end else begin // ---- 串行输出,MSB first ---- din <= sr[31]; sr <= {sr[30:0], 1'b0}; end end end endmodule“声音频率器”APP获得的频谱,很明显看出频谱很干净:
5.生成DTMF信号,利用5个按键形成16个组合:
| key[4:0] | 符号 | 低频 | 高频 |
| 00000 | 1 | 697 | 1209 |
| 00001 | 2 | 697 | 1336 |
| 00010 | 3 | 697 | 1477 |
| 00011 | A | 697 | 1633 |
| 00100 | 4 | 770 | 1209 |
| 00101 | 5 | 770 | 1336 |
| 00110 | 6 | 770 | 1477 |
| 00111 | B | 770 | 1633 |
| 01000 | 7 | 852 | 1209 |
| 01001 | 8 | 852 | 1336 |
| 01010 | 9 | 852 | 1477 |
| 01011 | C | 852 | 1633 |
| 01100 | * | 941 | 1209 |
| 01101 | 0 | 941 | 1336 |
| 01110 | # | 941 | 1477 |
| 01111 | D | 941 | 1633 |
| 1xxxx | 静音 | 静音 | 静音 |
代码如下:
//============================================================================= // dtmf_i2s.v -- DTMF 信号发生器(I2S 输出,直连 UDA1334A) //----------------------------------------------------------------------------- // 5 个按键 key[4:0] 组成 5 bit 编码: // key[4] = 1 -> 静音(无按键/无效码) // key[4] = 0 -> key[3:0] = 0~15 对应 16 个 DTMF 符号 // // key[3:2] = 行(低频组) key[1:0] = 列(高频组) // 列: 0 1 2 3 // 1209 Hz 1336 Hz 1477 Hz 1633 Hz // 行 0 697 Hz: 1 2 3 A // 行 1 770 Hz: 4 5 6 B // 行 2 852 Hz: 7 8 9 C // 行 3 941 Hz: * 0 # D // 即 key[3:0] = {行, 列},共 16 个符号 // // I2S:bclk = 3.072 MHz,一帧 64 bclk(左 32 + 右 32),LRCK = 48 kHz // 每声道:1 bit 延迟槽 + 24 bit 数据(MSB first) + 7 bit 补 0 // 两路正弦在数字域相加,峰值 0.5 FS(-6 dBFS) // // 频率精度:inc = round(f * 2^32 / 48000),量化误差 < 2e-4 Hz; // 实际 fs = 48000.5 Hz(PLL +11 ppm),音高同样偏高 11 ppm //============================================================================= `timescale 1ns / 1ps module dtmf_i2s ( input clk, // 50 MHz input rst_n, // 异步复位,低有效 input [4:0] key, // 5 个按键编码,0~15 有效,16~31 = 静音 output wire bclk, // 3.072 MHz 位时钟 output reg wsel, // LRCK / WS,0 = 左声道 output reg din // 串行数据 ); // ------------- 相位增量:inc = f * 2^32 / 48000 ------------- localparam [31:0] INC_697 = 32'd62366504; // 697 Hz (行1) localparam [31:0] INC_770 = 32'd68898434; // 770 Hz (行2) localparam [31:0] INC_852 = 32'd76235670; // 852 Hz (行3) localparam [31:0] INC_941 = 32'd84199255; // 941 Hz (行4) localparam [31:0] INC_1209 = 32'd108179489; // 1209 Hz (列1) localparam [31:0] INC_1336 = 32'd119543256; // 1336 Hz (列2) localparam [31:0] INC_1477 = 32'd132159723; // 1477 Hz (列3) localparam [31:0] INC_1633 = 32'd146118367; // 1633 Hz (列4) // ------------- 1/4 周期正弦表(64 点,等效 256 点全周期)------------- // 幅值 0x200000 = 0.25 FS(单音 -12 dBFS) function [23:0] qsin; input [5:0] k; // 0~63 begin case (k) 6'd 0 : qsin = 24'sh000000; 6'd 1 : qsin = 24'sh00C90B; 6'd 2 : qsin = 24'sh0191F6; 6'd 3 : qsin = 24'sh025AA4; 6'd 4 : qsin = 24'sh0322F5; 6'd 5 : qsin = 24'sh03EACA; 6'd 6 : qsin = 24'sh04B204; 6'd 7 : qsin = 24'sh057885; 6'd 8 : qsin = 24'sh063E2E; 6'd 9 : qsin = 24'sh0702E1; 6'd10 : qsin = 24'sh07C67E; 6'd11 : qsin = 24'sh0888E9; 6'd12 : qsin = 24'sh094A03; 6'd13 : qsin = 24'sh0A09AE; 6'd14 : qsin = 24'sh0AC7CD; 6'd15 : qsin = 24'sh0B8443; 6'd16 : qsin = 24'sh0C3EF1; 6'd17 : qsin = 24'sh0CF7BD; 6'd18 : qsin = 24'sh0DAE88; 6'd19 : qsin = 24'sh0E6337; 6'd20 : qsin = 24'sh0F15AF; 6'd21 : qsin = 24'sh0FC5D2; 6'd22 : qsin = 24'sh107388; 6'd23 : qsin = 24'sh111EB3; 6'd24 : qsin = 24'sh11C73B; 6'd25 : qsin = 24'sh126D05; 6'd26 : qsin = 24'sh130FF8; 6'd27 : qsin = 24'sh13AFFA; 6'd28 : qsin = 24'sh144CF3; 6'd29 : qsin = 24'sh14E6CB; 6'd30 : qsin = 24'sh157D69; 6'd31 : qsin = 24'sh1610B7; 6'd32 : qsin = 24'sh16A09E; 6'd33 : qsin = 24'sh172D08; 6'd34 : qsin = 24'sh17B5DF; 6'd35 : qsin = 24'sh183B0E; 6'd36 : qsin = 24'sh18BC80; 6'd37 : qsin = 24'sh193A22; 6'd38 : qsin = 24'sh19B3E0; 6'd39 : qsin = 24'sh1A29A8; 6'd40 : qsin = 24'sh1A9B66; 6'd41 : qsin = 24'sh1B090A; 6'd42 : qsin = 24'sh1B7283; 6'd43 : qsin = 24'sh1BD7C1; 6'd44 : qsin = 24'sh1C38B3; 6'd45 : qsin = 24'sh1C954B; 6'd46 : qsin = 24'sh1CED7B; 6'd47 : qsin = 24'sh1D4135; 6'd48 : qsin = 24'sh1D906C; 6'd49 : qsin = 24'sh1DDB14; 6'd50 : qsin = 24'sh1E2121; 6'd51 : qsin = 24'sh1E6289; 6'd52 : qsin = 24'sh1E9F41; 6'd53 : qsin = 24'sh1ED741; 6'd54 : qsin = 24'sh1F0A7F; 6'd55 : qsin = 24'sh1F38F4; 6'd56 : qsin = 24'sh1F6298; 6'd57 : qsin = 24'sh1F8765; 6'd58 : qsin = 24'sh1FA755; 6'd59 : qsin = 24'sh1FC264; 6'd60 : qsin = 24'sh1FD88E; 6'd61 : qsin = 24'sh1FE9CE; 6'd62 : qsin = 24'sh1FF622; 6'd63 : qsin = 24'sh1FFD88; default: qsin = 24'h000000; endcase end endfunction // ------------- 由 32 bit 相位映射到 256 点正弦(象限对称)------------- function [23:0] sine256; input [31:0] p; // p[31:24] = 全周期索引 begin case (p[31:30]) // 象限 2'd0 : sine256 = qsin(p[29:24]); 2'd1 : sine256 = qsin(6'd63 - p[29:24]); 2'd2 : sine256 = -qsin(p[29:24]); default: sine256 = -qsin(6'd63 - p[29:24]); endcase end endfunction // ------------- 内部信号 ------------- reg [5:0] bcnt; // 帧内 bclk 序号 0~63 reg [31:0] ph1, ph2; // 两路相位累加器 reg [31:0] inc1, inc2;// 两路相位增量 reg [23:0] samp; // 当前样点(左右声道共用) reg [31:0] sr; // 并串移位寄存器 reg mute; // 1 = 静音 reg [4:0] key_s0, key_s1, key_d; // 按键同步 + 去抖 reg [7:0] dbt; wire [23:0] sin1 = sine256(ph1); wire [23:0] sin2 = sine256(ph2); wire [23:0] mix = mute ? 24'd0 : (sin1 + sin2); // ------------- PLL ------------- PLL_1 PLL1 ( .refclk (clk), .reset (~rst_n), .clk0_out(bclk) ); // ------------- I2S 帧生成 + 按键去抖 ------------- always @(negedge bclk or negedge rst_n) begin if (!rst_n) begin bcnt <= 6'd0; ph1 <= 32'd0; ph2 <= 32'd0; inc1 <= 32'd0; inc2 <= 32'd0; mute <= 1'b1; samp <= 24'd0; sr <= 32'd0; wsel <= 1'b0; din <= 1'b0; key_s0 <= 5'h1F; key_s1 <= 5'h1F; key_d <= 5'h1F; // 复位后视为无按键 dbt <= 8'd0; end else begin bcnt <= (bcnt == 6'd63) ? 6'd0 : bcnt + 6'd1; // ---- 按键同步(3 MHz 打两拍)+ 按帧去抖(240 帧 ≈ 5 ms)---- key_s0 <= key; key_s1 <= key_s0; if (bcnt == 6'd63) begin if (key_s1 != key_d) begin if (dbt == 8'd239) begin key_d <= key_s1; dbt <= 8'd0; end else begin dbt <= dbt + 8'd1; end end else begin dbt <= 8'd0; end end if (bcnt == 6'd63) begin // ---- 左声道起点,更新样点 ---- wsel <= 1'b0; din <= 1'b0; // I2S 1 bit 延迟槽 samp <= mix; sr <= {mix, 8'h00}; // 24 bit 左对齐 if (key_d[4]) begin // 无按键 / 无效码 -> 静音 inc1 <= 32'd0; inc2 <= 32'd0; mute <= 1'b1; ph1 <= 32'd0; // 归零,下次起音从零相位开始 ph2 <= 32'd0; end else begin mute <= 1'b0; case (key_d[3:2]) // 低频组(行) 2'd0 : inc1 <= INC_697; 2'd1 : inc1 <= INC_770; 2'd2 : inc1 <= INC_852; default: inc1 <= INC_941; endcase case (key_d[1:0]) // 高频组(列) 2'd0 : inc2 <= INC_1209; 2'd1 : inc2 <= INC_1336; 2'd2 : inc2 <= INC_1477; default: inc2 <= INC_1633; endcase ph1 <= ph1 + inc1; // 相位连续,换键不跳变 ph2 <= ph2 + inc2; end end else if (bcnt == 6'd31) begin // ---- 右声道起点 ---- wsel <= 1'b1; din <= 1'b0; // 1 bit 延迟槽 sr <= {samp, 8'h00}; // 左右同一份数据(单声道) end else begin // ---- 串行输出,MSB first ---- din <= sr[31]; sr <= {sr[30:0], 1'b0}; end end end endmodule“声音频率器”APP获得的“D”和“1”的频谱:
6.也可充分利用安路TD软件自带的DDS:
代码如下:
//============================================================================= // dtmf_i2s_dds.v -- DTMF 信号发生器(I2S 输出,直连 UDA1334A) // 正弦改用 TD 自带软核 "DDS - Direct Digital Synthesis" //----------------------------------------------------------------------------- // 5 个按键 key[4:0] 组成 5 bit 编码: // key[4] = 1 -> 静音(无按键/无效码) // key[4] = 0 -> key[3:0] = 0~15 对应 16 个 DTMF 符号 // // key[3:2] = 行(低频组) key[1:0] = 列(高频组) // 列: 0 1 2 3 // 1209 Hz 1336 Hz 1477 Hz 1633 Hz // 行 0 697 Hz: 1 2 3 A // 行 1 770 Hz: 4 5 6 B // 行 2 852 Hz: 7 8 9 C // 行 3 941 Hz: * 0 # D // 即 key[3:0] = {行, 列},共 16 个符号 // // I2S:bclk = 3.072 MHz,一帧 64 bclk(左 32 + 右 32),LRCK = 48 kHz // 每声道:1 bit 延迟槽 + 24 bit 数据(MSB first) + 7 bit 补 0 // 两路正弦在数字域相加,峰值 0.5 FS(-6 dBFS) // //----------------------------------------------------------------------------- // 与旧版(自编查找表 DDS)的差异: // 1) 删除 qsin / sine256 查找表、ph1/ph2 相位累加器、sin1/sin2 组合逻辑, // 正弦完全由 DDS IP 产生(省 LUT,频率精度由 IP 的 32 bit 相位累加器保证) // 2) DDS IP 的配置为 FREC_SYS = 3.072 MHz(与 bclk 一致), // 因此频率字: fword = round(f * 2^32 / 3072000) // —— 注意不是除以 48000!IP 每个 clk(= 每个 bclk)累加一次相位 // 3) IP 输出 wave_sin[23:0] 为 ±(2^23-1) 满量程有符号正弦, // 右移 GAIN_SHIFT(=2) 位得到 0.25 FS(-12 dBFS)单音,两音相加 <= 0.5 FS // (旧版单音幅值是 0x200000,右移 2 位后为 0x1FFFFF,两者等效) // 4) DTMF 要同时送出 行音 + 列音,而 IP 一次只产生一个频率, // 故把同一个 IP 模块例化两次(ROM/相位累加器各一套),行为不变 // 5) 静音处理:频率字置 0(相位冻结)+ 数据通路输出 0,保证数字静音 //============================================================================= `timescale 1ns / 1ps module dtmf_i2s_dds ( input clk, // 50 MHz input rst_n, // 异步复位,低有效 input [4:0] key, // 5 个按键编码,0~15 有效,16~31 = 静音 output wire bclk, // 3.072 MHz 位时钟 output reg wsel, // LRCK / WS,0 = 左声道 output wire vcc, output wire gnd, output reg din // 串行数据 ); // ------------- 频率字:fword = round(f * 2^32 / 3.072 MHz) ------------- localparam [31:0] FW_697 = 32'd974477; // 697.0003 Hz (行1) localparam [31:0] FW_770 = 32'd1076538; // 770.0000 Hz (行2) localparam [31:0] FW_852 = 32'd1191182; // 852.0000 Hz (行3) localparam [31:0] FW_941 = 32'd1315613; // 941.0000 Hz (行4) localparam [31:0] FW_1209 = 32'd1690305; // 1209.0003 Hz (列1) localparam [31:0] FW_1336 = 32'd1867863; // 1336.0000 Hz (列2) localparam [31:0] FW_1477 = 32'd2064996; // 1477.0000 Hz (列3) localparam [31:0] FW_1633 = 32'd2283099; // 1633.0000 Hz (列4) // ------------- 单音衰减 ------------- // IP 输出满量程 ±0x7FFFFF,右移 2 位 => ±0x1FFFFF = 0.25 FS(-12 dBFS) // 若板子上声音偏小/偏大,只改这个值即可(1=0.5FS, 3=-18dBFS) localparam GAIN_SHIFT = 2; // ------------- 内部信号 ------------- reg [5:0] bcnt; // 帧内 bclk 序号 0~63 reg [31:0] sr; // 并串移位寄存器 reg [23:0] samp; // 当前样点(左右声道共用) reg [4:0] key_s0, key_s1, key_d; // 按键同步 + 去抖 reg [7:0] dbt; reg [31:0] fw_row, fw_col; // 行/列 DDS 频率字 wire [23:0] sin_row, sin_col; // DDS IP 输出(有符号 24 bit) wire signed [23:0] s_row = sin_row; wire signed [23:0] s_col = sin_col; wire signed [23:0] att_row = s_row >>> GAIN_SHIFT; wire signed [23:0] att_col = s_col >>> GAIN_SHIFT; wire mute_now = key_d[4]; // 1 = 静音(无按键 / 无效码) wire signed [23:0] mix = mute_now ? 24'sd0 : (att_row + att_col); // ------------- PLL:50 MHz -> 3.072 MHz 位时钟 ------------- PLL_1 PLL1 ( .refclk (clk), .reset (~rst_n), .clk0_out(bclk) ); // ------------- DDS IP 例化(行音 697~941 Hz / 列音 1209~1633 Hz)------- // 同一个 IP 模块例化两次;若你已在 IP 向导里另外生成了一份,也可直接用那一个 DDS_Direct_Digital_Synthesis_0 DDS_ROW ( .clk (bclk), .fword (fw_row), .rst_n (rst_n), .wave_sin(sin_row) ); DDS_Direct_Digital_Synthesis_0 DDS_COL ( .clk (bclk), .fword (fw_col), .rst_n (rst_n), .wave_sin(sin_col) ); // ------------- I2S 帧生成 + 按键去抖 ------------- always @(negedge bclk or negedge rst_n) begin if (!rst_n) begin bcnt <= 6'd0; samp <= 24'd0; sr <= 32'd0; wsel <= 1'b0; din <= 1'b0; key_s0 <= 5'h1F; key_s1 <= 5'h1F; key_d <= 5'h1F; // 复位后视为无按键 dbt <= 8'd0; fw_row <= 32'd0; // 频率字为 0 -> 相位冻结,输出直流 0 fw_col <= 32'd0; end else begin bcnt <= (bcnt == 6'd63) ? 6'd0 : bcnt + 6'd1; // ---- 按键同步(3 MHz 打两拍)+ 按帧去抖(240 帧 ≈ 5 ms)---- key_s0 <= key; key_s1 <= key_s0; if (bcnt == 6'd63) begin if (key_s1 != key_d) begin if (dbt == 8'd239) begin key_d <= key_s1; dbt <= 8'd0; end else begin dbt <= dbt + 8'd1; end end else begin dbt <= 8'd0; end end if (bcnt == 6'd63) begin // ---- 左声道起点,更新样点 ---- wsel <= 1'b0; din <= 1'b0; // I2S 1 bit 延迟槽 samp <= mix; // 静音时 mix = 0 sr <= {mix, 8'h00}; // 24 bit 左对齐 if (mute_now) begin // 无按键 / 无效码 -> 静音 fw_row <= 32'd0; fw_col <= 32'd0; end else begin case (key_d[3:2]) // 低频组(行) 2'd0 : fw_row <= FW_697; 2'd1 : fw_row <= FW_770; 2'd2 : fw_row <= FW_852; default: fw_row <= FW_941; endcase case (key_d[1:0]) // 高频组(列) 2'd0 : fw_col <= FW_1209; 2'd1 : fw_col <= FW_1336; 2'd2 : fw_col <= FW_1477; default: fw_col <= FW_1633; endcase end end else if (bcnt == 6'd31) begin // ---- 右声道起点 ---- wsel <= 1'b1; din <= 1'b0; // 1 bit 延迟槽 sr <= {samp, 8'h00}; // 左右同一份数据(单声道) end else begin // ---- 串行输出,MSB first ---- din <= sr[31]; sr <= {sr[30:0], 1'b0}; end end end assign vcc = 1'b1; assign gnd = 1'b0; endmodule7.其他:
FPGA的IO足以驱动UDA1334,例如可以按照以下配置引脚,在代码里把vcc和gnd分别置为1和0即可。
set_pin_assignment { bclk } { LOCATION = G5; IOSTANDARD = LVCMOS33; DRIVESTRENGTH = 8; PULLTYPE = NONE;} set_pin_assignment { din } { LOCATION = D1; IOSTANDARD = LVCMOS33; DRIVESTRENGTH = 8; PULLTYPE = NONE; } set_pin_assignment { rst_n } { LOCATION = M1; IOSTANDARD = LVCMOS33; PULLTYPE = PULLUP; } set_pin_assignment { wsel } { LOCATION = C2; IOSTANDARD = LVCMOS33; DRIVESTRENGTH = 8; PULLTYPE = NONE; } set_pin_assignment { vcc } { LOCATION = B7; IOSTANDARD = LVCMOS33; DRIVESTRENGTH = 8; PULLTYPE = NONE; } set_pin_assignment { gnd } { LOCATION = B1; IOSTANDARD = LVCMOS33; DRIVESTRENGTH = 8; PULLTYPE = NONE; } set_pin_assignment { clk } { LOCATION = E1; IOSTANDARD = LVCMOS33; PULLTYPE = PULLUP; } set_pin_assignment { key[4] } { LOCATION = M2; IOSTANDARD = LVCMOS33; PULLTYPE = PULLUP; } set_pin_assignment { key[3] } { LOCATION = K10; IOSTANDARD = LVCMOS33; PULLTYPE = PULLUP; } set_pin_assignment { key[2] } { LOCATION = L10; IOSTANDARD = LVCMOS33; PULLTYPE = PULLUP; } set_pin_assignment { key[1] } { LOCATION = M16; IOSTANDARD = LVCMOS33; PULLTYPE = PULLUP; } set_pin_assignment { key[0] } { LOCATION = M15; IOSTANDARD = LVCMOS33; PULLTYPE = PULLUP; }在Linux里运行TD软件,BitWriter时,速度选为1M,速度太高了则无法下载。顺便说一下,Linux下的TD配置稍微有点复杂,此时完全可以使用WorkBuddy之类的Agent来操作完成。