如何去制作一套完整的基于FPGA的DDS信号发生器系统


如何去制作一套完整的基于FPGA的DDS信号发生器系统

时间:2025-03-13  作者:Diven  阅读:0

为帮助同学们更好地理解和实现DDS的功能,在硬禾做毕业设计的小杨同学特制作了一套完整的DDS任意波形发生器系统,它包含如下的功能:

1. 顶层模块

FPGA片内逻辑的结构框图如上图所示,FPGA片上实现的逻辑包括:

调用内部锁相环由输入的12M时钟得到120MHZ工作时钟,驱动DDS控制模块,输出作为DAC的转换时钟。

处理旋转编码器的输入。判断旋转编码器的转动和按下,顺时针转动对应信号频率的增加,逆时针转动对应信号频率的减小,按下对应波形的切换。

存储正弦波的波形数据。

实现的波形包括正弦波、三角波、方波、锯齿波四种波形,波形频率范围为100kHZ到10MHZ。

通过spi总线驱动OLED屏幕指示当前的波形和频率。
2. DDS核心模块

DDS控制模块是信号发生器设计的核心,利用Verilog实现DDS的结构框图如下图所示。
 

从图中可以看出,DDS控制模块根据旋转编码器模块的输入信号调节频率控制字、切换波形,输出一定频率的方波、三角波、锯齿波或正弦波数据至DAC。同时,DDS控制模块还会将当前波形信息和频率信息输出给OLED模块用于显示。四种波形的产生使用同一个相位累加寄存器,如下图所示,相位累加寄存器本质上是一个不断累加的计数器,单次累加的幅度是频率控制字,频率控制字由旋转编码器调节,当旋转编码器顺时针转动时增大频率控制字,当旋转编码器逆时针转动时减小频率控制字。接下来分别介绍四种波形的原理。

方波 - 取相位累加寄存器的最高位作为判断条件,当最高位为逻辑1,则对DAC的输入赋值10’h3ff;当最高位为逻辑0,则则对DAC的输入赋值10’h000,从而实现了方波波形。

锯齿波 - 取相位累加寄存器的高十位直接作为DAC的输入,随着相位累加寄存器不断累加,DAC的输入也周期性地以锯齿波形状循环。

三角波 - 取相位累加寄存器的最高位作为判断条件,当最高位为逻辑1,则将相位累加寄存器的13到22位直接作为DAC数据的输入;当最高位为逻辑0,则将相位累加寄存器的13到22位取反后作为DAC数据的输入。

正弦波 - 与其它三种波形不同,正弦波不能直接用取相位累加寄存器作为DAC的输入,需要将相位累加寄存器的高8位作为正弦波表的地址输入,这样的话,就实现了在一个周期内以一定间隔读出正弦波表内的数据作为DAC的输入。

下面是DDS的主代码,可以选择输出的波形以及相应的频率

// --------------------------------------------------------------------
// >>>>>>>>>>>>>>>>>>>>>>>>> COPYRIGHT NOTICE <<<<<<<<<<<<<<<<<<<<<<<<<
// --------------------------------------------------------------------
// Module: dds
//
// Author: Step
//
// Description: dds
//
// Web
//
// --------------------------------------------------------------------
// Code Revision History :
// --------------------------------------------------------------------
// Version: |Mod. Date: |Changes Made:
// V1.0 |2021.11.27 |Initial ver
// --------------------------------------------------------------------
module dds(clk_in,rst_n,O_pulse,L_pulse,R_pulse,dac_data,wave,WaveFreq);//
localparam SIN = 2'b00, SAW = 2'b01, TRI = 2'b10, SQU = 2'b11;
input clk_in; // 小脚丫FPGA的外部时钟频率为12MHz
input rst_n;
input wire O_pulse,L_pulse,R_pulse;
output reg [9:0] dac_data; // 10位数据输出送给外部的DAC
output reg [1:0] wave;
output reg [26:0] WaveFreq;
reg [23:0] phase_acc; //增加相位累加器位数使得分辨率提高
wire [23:0] phase;
reg [23:0] f_inc;
assign phase=phase_acc;
always @(posedge clk_in) phase_acc <= phase_acc + f_inc; //f_inc=24'd27962;主时钟为12MHz,则产生20KHz的正弦波信号
wire [9:0] sin_dat; //正弦波
wire [9:0] saw_dat = phase[23:14]; //锯齿波
wire [9:0] tri_dat = phase[23]? (~phase[22:13]) : phase[22:13]; //三角波
wire [9:0] squ_dat = phase[23]? 10'h3ff : 10'h000; //方波
always @(*) begin
case(wave)
2'b00: dac_data = sin_dat; //正弦波
2'b01: dac_data = saw_dat; //锯齿波
2'b10: dac_data = tri_dat; //三角波
2'b11: dac_data = squ_dat; //方波
default: dac_data = sin_dat; //正弦波
endcase
end
lookup_tables u_lookup_tables(.phase(phase_acc[23:16]), .sin_out(sin_dat));
//波形输出选择
always @(posedge clk_in or negedge rst_n) begin
if(!rst_n) wave <= SIN;
else if(O_pulse)begin
case(wave)
SIN: wave <= SAW;
SAW: wave <= TRI;
TRI: wave <= SQU;
SQU: wave <= SIN;
default: wave <= SIN;
endcase
end else wave <= wave;
end
//频率控制
always@(posedge clk_in or negedge rst_n) begin
if(!rst_n) begin
f_inc <= 24'h22222;
WaveFreq<=1_000_000;
end
else if(L_pulse==1'b1) begin
if(f_inc <= 24'h369d) f_inc <= f_inc;
else begin
f_inc <= f_inc - 24'h369d;
WaveFreq<=WaveFreq-100000;
end
end
else if(R_pulse==1'b1) begin
if(f_inc >= 24'h155554) f_inc <= f_inc;
else begin
f_inc <= f_inc + 24'h369d;
WaveFreq<=WaveFreq+100000;
end
end
else f_inc <= f_inc;
end
endmodule
//dds时钟频率给定后,输出信号的频率取决于频率控制字,
// 频率分辨率取决于累加器位数,
// 相位分辨率取决于ROM的地址线位数,
// 幅度量化噪声取决于ROM的数据位字长和D/A转换器位数
查找表的程序,主要是利用了正弦波的对称性,为了节省FPGA的资源,可以只构建一个象限的波形,另外3个象限可以基于其对称性通过逻辑来实现。


module lookup_tables(phase, sin_out);
input [7:0] phase;
output [9:0] sin_out;
wire [9:0] sin_out;
reg [5:0] address;
wire [1:0] sel;
wire [8:0] sine_table_out;
reg [9:0] sine_onecycle_amp;
//assign sin_out = {4'b0, sine_onecycle_amp[9:4]} + 9'hff; // 可以调节输出信号的幅度
assign sin_out = sine_onecycle_amp[9:0];
assign sel = phase[7:6];
sin_table u_sin_table(address,sine_table_out);
always @(sel or sine_table_out)
begin
case(sel)
2'b00: begin
sine_onecycle_amp = 9'h1ff + sine_table_out[8:0];
address = phase[5:0];
end
2'b01: begin
sine_onecycle_amp = 9'h1ff + sine_table_out[8:0];
address = ~phase[5:0];
end
2'b10: begin
sine_onecycle_amp = 9'h1ff - sine_table_out[8:0];
address = phase[5:0];
end
2'b11: begin
sine_onecycle_amp = 9'h1ff - sine_table_out[8:0];
address = ~ phase[5:0];
end
endcase
end
endmodule

构建正弦波表的程序,无论使用Lattice的Diamond以及Inel的Quartus编译工具中都有相应的IP可以直接调用使用。在这里我们通过Verilog代码自行构建一个正弦波表。

module sin_table(address,sin);
output [8:0] sin;
input [5:0] address;
reg [8:0] sin;
always @(address)
begin
case(address)
6'h0: sin=9'h0;
6'h1: sin=9'hC;
6'h2: sin=9'h19;
6'h3: sin=9'h25;
6'h4: sin=9'h32;
6'h5: sin=9'h3E;
6'h6: sin=9'h4B;
6'h7: sin=9'h57;
6'h8: sin=9'h63;
6'h9: sin=9'h70;
6'ha: sin=9'h7C;
6'hb: sin=9'h88;
6'hc: sin=9'h94;
6'hd: sin=9'hA0;
6'he: sin=9'hAC;
6'hf: sin=9'hB8;
6'h10: sin=9'hC3;
6'h11: sin=9'hCF;
6'h12: sin=9'hDA;
6'h13: sin=9'hE6;
6'h14: sin=9'hF1;
6'h15: sin=9'hFC;
6'h16: sin=9'h107;
6'h17: sin=9'h111;
6'h18: sin=9'h11C;
6'h19: sin=9'h126;
6'h1a: sin=9'h130;
6'h1b: sin=9'h13A;
6'h1c: sin=9'h144;
6'h1d: sin=9'h14E;
6'h1e: sin=9'h157;
6'h1f: sin=9'h161;
6'h20: sin=9'h16A;
6'h21: sin=9'h172;
6'h22: sin=9'h17B;
6'h23: sin=9'h183;
6'h24: sin=9'h18B;
6'h25: sin=9'h193;
6'h26: sin=9'h19B;
6'h27: sin=9'h1A2;
6'h28: sin=9'h1A9;
6'h29: sin=9'h1B0;
6'h2a: sin=9'h1B7;
6'h2b: sin=9'h1BD;
6'h2c: sin=9'h1C3;
6'h2d: sin=9'h1C9;
6'h2e: sin=9'h1CE;
6'h2f: sin=9'h1D4;
6'h30: sin=9'h1D9;
6'h31: sin=9'h1DD;
6'h32: sin=9'h1E2;
6'h33: sin=9'h1E6;
6'h34: sin=9'h1E9;
6'h35: sin=9'h1ED;
6'h36: sin=9'h1F0;
6'h37: sin=9'h1F3;
6'h38: sin=9'h1F6;
6'h39: sin=9'h1F8;
6'h3a: sin=9'h1FA;
6'h3b: sin=9'h1FC;
6'h3c: sin=9'h1FD;
6'h3d: sin=9'h1FE;
6'h3e: sin=9'h1FF;
6'h3f: sin=9'h1FF;
endcase
end
endmodule

3. 编码器输入模块


// --------------------------------------------------------------------
// >>>>>>>>>>>>>>>>>>>>>>>>> COPYRIGHT NOTICE <<<<<<<<<<<<<<<<<<<<<<<<<
// --------------------------------------------------------------------
// Module: Encoder
//
// Author: Step
//
// Description: Driver for rotary encoder
//
// Web
//
// --------------------------------------------------------------------
// Code Revision History :
// --------------------------------------------------------------------
// Version: |Mod. Date: |Changes Made:
// V1.0 |2021.11.27 |Initial ver
// --------------------------------------------------------------------
module Encoder
(
input clk_in, //系统时钟
input rst_n_in, //系统复位,低有效
input wire key_a,
input wire key_b,
input wire key_ok,
output reg Left_pulse, //左旋转脉冲输出
output reg Right_pulse, //右旋转脉冲输出
output OK_pulse //按动脉冲输出
);
localparam NUM_500US = 6_0000;
reg [15:0] cnt;
//计数器周期为500us,控制键值采样频率
always@(posedge clk_in or negedge rst_n_in) begin
if(!rst_n_in) cnt <= 0;
else if(cnt >= NUM_500US-1) cnt <= 1'b0;
else cnt <= cnt + 1'b1;
end
reg [5:0] cnt_20ms;
reg key_a_r,key_a_r1;
reg key_b_r,key_b_r1;
reg key_ok_r;
//针对A、B、D管脚分别做简单去抖操作,
//如果对旋转编码器的要求比较高,建议现对旋转编码器的输出做严格的消抖处理后再来做旋转编码器的驱动
//对旋转编码器的输入缓存,消除亚稳态同时延时锁存
always@(posedge clk_in or negedge rst_n_in) begin
if(!rst_n_in) begin
key_a_r <= 1'b1;
key_a_r1 <= 1'b1;
key_b_r <= 1'b1;
key_b_r1 <= 1'b1;
cnt_20ms <= 1'b1;
key_ok_r <= 1'b1;
end else if(cnt == NUM_500US-1) begin
key_a_r <= key_a;
key_a_r1 <= key_a_r;
key_b_r <= key_b;
key_b_r1 <= key_b_r;
if(cnt_20ms >= 6'd40) begin //对于按键D信号还是采用20ms周期采样的方法,40*500us = 20ms
cnt_20ms <= 6'd0;
key_ok_r <= key_ok;
end else begin
cnt_20ms <= cnt_20ms + 1'b1;
key_ok_r <= key_ok_r;
end
end
end
reg key_ok_r1;
//对按键D信号进行延时锁存
always@(posedge clk_in or negedge rst_n_in) begin
if(!rst_n_in) key_ok_r1 <= 1'b1;
else key_ok_r1 <= key_ok_r;
end
wire A_state = key_a_r1 && key_a_r && key_a; //旋转编码器A信号高电平状态检测
wire B_state = key_b_r1 && key_b_r && key_b; //旋转编码器B信号高电平状态检测
assign OK_pulse = key_ok_r1 && (!key_ok_r); //旋转编码器D信号下降沿检测
reg A_state_reg;
//延时锁存
always@(posedge clk_in or negedge rst_n_in) begin
if(!rst_n_in) A_state_reg <= 1'b1;
else A_state_reg <= A_state;
end
//旋转编码器A信号的上升沿和下降沿检测
wire A_pos = (!A_state_reg) && A_state;
wire A_neg = A_state_reg && (!A_state);
//通过旋转编码器A信号的边沿和B信号的电平状态的组合判断旋转编码器的操作,并输出对应的脉冲信号
always@(posedge clk_in or negedge rst_n_in)begin
if(!rst_n_in)begin
Right_pulse <= 1'b0;
Left_pulse <= 1'b0;
end else begin
if(A_pos && B_state) Left_pulse <= 1'b1;
else if(A_neg && B_state) Right_pulse <= 1'b1;
else begin
Right_pulse <= 1'b0;
Left_pulse <= 1'b0;
end
end
end
endmodule

4. 将频率值转换为OLED显示器上的字符

module getChar3 (
input clk,
input rst_n,
input [31:0] WaveFreq,
output reg[(8*16-1):0] char
);
reg [31:0] t_bin;
reg [3:0] bcd8,bcd7,bcd6,bcd5,bcd4,bcd3,bcd2,bcd1;
reg [3:0] state;
always@(posedge clk or negedge rst_n)
begin
if(!rst_n) begin
t_bin<=WaveFreq;
char<= 0;
bcd8<= 0;
bcd7<= 0;
bcd6<= 0;
bcd5<= 0;
bcd4<= 0;
bcd3<= 0;
bcd2<= 0;
bcd1<= 0;
state<=0;
end
else begin
case (state)
0: begin
t_bin<=WaveFreq;
state<=state+1;
end
1: begin
if($signed(t_bin)-$signed(10000000)>=0)begin
bcd8<=bcd8+1;
t_bin<=t_bin-10000000;
end
else begin
state<=state+1;
end
end
2: begin
if($signed(t_bin)-$signed(1000000)>=0)begin
bcd7<=bcd7+1;
t_bin<=t_bin-1000000;
end
else begin
state<=state+1;
end
end
3: begin
if($signed(t_bin)-$signed(100000)>=0)begin
bcd6<=bcd6+1;
t_bin<=t_bin-100000;
end
else begin
state<=state+1;
end
end
4: begin
if($signed(t_bin)-$signed(10000)>=0)begin
bcd5<=bcd5+1;
t_bin<=t_bin-10000;
end
else begin
state<=state+1;
end
end
5: begin
if($signed(t_bin)-$signed(1000)>=0)begin
bcd4<=bcd4+1;
t_bin<=t_bin-1000;
end
else begin
state<=state+1;
end
end
6: begin
if($signed(t_bin)-$signed(100)>=0)begin
bcd3<=bcd3+1;
t_bin<=t_bin-100;
end
else begin
state<=state+1;
end
end
7: begin
if($signed(t_bin)-$signed(10)>=0)begin
bcd2<=bcd2+1;
t_bin<=t_bin-10;
end
else begin
state<=state+1;
end
end
8: begin
if($signed(t_bin)-$signed(1)>=0)begin
bcd1<=bcd1+1;
t_bin<=t_bin-1;
end
else begin
state<=state+1;
end
end
9:begin
t_bin<=WaveFreq;
char<={" ",4'h0,bcd8,4'h0,bcd7,"_",
4'h0,bcd6,4'h0,bcd5,4'h0,bcd4,"_",
4'h0,bcd3,4'h0,bcd2,4'h0,bcd1,"Hz "};
bcd8<= 0;
bcd7<= 0;
bcd6<= 0;
bcd5<= 0;
bcd4<= 0;
bcd3<= 0;
bcd2<= 0;
bcd1<= 0;
state<=0;
end
default: begin
t_bin<=WaveFreq;
bcd7<= 0;
bcd6<= 0;
bcd5<= 0;
bcd4<= 0;
bcd3<= 0;
bcd2<= 0;
bcd1<= 0;
state<=0;
end
endcase
end
end
endmodule

5. OLED显示模块

这是通过SPI总线方式来驱动128*64分辨率的OLED显示屏显示相应信息的逻辑代码

该模块的输入包括时钟、复位、输入变量,输出是驱动ssd1306的的五根总线:包括spi的三条总线、一条ssd1306复位线、一条ssd1306数据指令选择线。该模块通过SPI总线完成OLED屏幕的显示,能够显示字符和汉字。该模块中,利用查找表实现字符、汉字模的存储,利用一个一段式状态机实现:初始化配置SSD1306驱动芯片、在屏幕上显示指定的信息。其中,IDLE状态作为初始状态完成所有寄存器变量的初始化;MAIN状态作为总调度实现了整个OLED模块的流程调度,如果想要使用本模块,只需要在本状态进行修改;INIT状态通过调用WRITE状态将23条SSD1306的配置指令通过SPI时序发出,完成SSD1306的配置;SCAN状态通过调用WRITE状态完成字符的显示;CHINESE状态通过调用WRITE状态完成汉字的显示;WRITE状态作为底层状态,完成SPI的传输过程;DELAY状态的功能是延时。本模块的使用通过在MAIN状态里进行添加相应的逻辑来实现,如果显示汉字则需要在汉字字模中添加相应的字模信息。如果想要实时刷新信息,需要将动态显示的信息通过本module的输入端口sw传入模块本在MAIN状态里进行调用,也可自行添加输入端口变量。

// --------------------------------------------------------------------

// Module: OLED12864 

// Description: OLED12864_Driver

// --------------------------------------------------------------------

module OLED12864

(

  input        clk,    //12MHz绯荤粺鏃堕挓

  input        rst_n,    //绯荤粺澶嶄綅锛屼綆鏈夋晥

  input    [3:0]  sw,

  input    [1:0]  wave,

  input [26:0]    WaveFreq,

  output  reg      oled_csn,  //OLCD娑叉櫠灞忎娇鑳

  output  reg      oled_rst,  //OLCD娑叉櫠灞忓浣

  output  reg      oled_dcn,  //OLCD鏁版嵁鎸囦护鎺у埗

  output  reg      oled_clk,  //OLCD鏃堕挓淇″彿

  output  reg      oled_dat  //OLCD鏁版嵁淇″彿

);

  localparam INIT_DEPTH = 16'd23; //LCD鍒濆鍖栫殑鍛戒护鐨勬暟閲

  localparam IDLE = 7'h1, MAIN = 7'h2, INIT = 7'h4, SCAN = 7'h8, WRITE = 7'h10, DELAY = 7'h20,CHINESE=7'h40;

  localparam HIGH  = 1'b1, LOW = 1'b0;

  localparam DATA  = 1'b1, CMD = 1'b0;

  reg   [7:0]       cmd [24:0];

  reg   [39:0]       mem [122:0];

  reg    [63:0]      mem_hanzi[79:0];

  reg    [4:0]      length_hanzi;

  reg  [7:0]        y_p, x_ph, x_pl;

  reg  [(8*21-1):0]   char;

  reg  [7:0]        num, char_reg;        

  reg  [4:0]        cnt_main, cnt_init, cnt_scan, cnt_write,cnt_chinese;

  reg  [15:0]      num_delay, cnt_delay, cnt;

  reg  [6:0]       state, state_back;

  reg [4:0]      hanzi_fuzhujishu;

    wire [(8*16-1):0]   char3;

  getChar3 getChar3 (

        .clk(clk),

        .rst_n(rst_n),

        .WaveFreq(WaveFreq),

        .char(char3)

    );

  always @ (posedge clk or negedge rst_n) begin

    if(!rst_n) begin

      cnt_main <= 1'b0; cnt_init <= 1'b0; cnt_scan <= 1'b0; cnt_write <= 1'b0;cnt_chinese <= 1'b0;

      y_p <= 1'b0; x_ph <= 1'b0; x_pl <= 1'b0;length_hanzi<=5'd0;hanzi_fuzhujishu<=5'd0;

      num <= 1'b0; char <= 1'b0; char_reg <= 1'b0;

      num_delay <= 16'd5; cnt_delay <= 1'b0; cnt <= 1'b0;

      oled_csn <= HIGH; oled_rst <= HIGH; oled_dcn <= CMD; oled_clk <= HIGH; oled_dat <= LOW;

      state <= IDLE; state_back <= IDLE;

    end else begin

      case(state)

        IDLE:begin

            cnt_main <= 1'b0; cnt_init <= 1'b0; cnt_scan <= 1'b0; cnt_write <= 1'b0;

            y_p <= 1'b0; x_ph <= 1'b0; x_pl <= 1'b0;

            num <= 1'b0; char <= 1'b0; char_reg <= 1'b0;

            num_delay <= 16'd5; cnt_delay <= 1'b0; cnt <= 1'b0;

            oled_csn <= HIGH; oled_rst <= HIGH; oled_dcn <= CMD; oled_clk <= HIGH; oled_dat <= LOW;

            state <= MAIN; state_back <= MAIN;

          end

        MAIN:begin

            if(cnt_main >= 5'd14) cnt_main <= 5'd12;

            else cnt_main <= cnt_main + 1'b1;

            case(cnt_main)  //MAIN鐘舵€            

              5'd0 :  begin state <= INIT; end

              5'd1 :  begin y_p <= 8'hb0; x_ph <= 8'h10; x_pl <= 8'h00; num <= 5'd8; char <= "DDS     ";state <= SCAN; end

              5'd2 :  begin y_p <= 8'hb1; x_ph <= 8'h10; x_pl <= 8'h00; num <= 5'd8; char <= "        ";state <= SCAN; end                                                

              5'd3 :  begin y_p <= 8'hb0; x_ph <= 8'h14; x_pl <= 8'h00; num <= 5'd8; char <= "         ";state <= SCAN; end

              5'd4 :  begin y_p <= 8'hb1; x_ph <= 8'h14; x_pl <= 8'h00; num <= 5'd8; char <= "         ";state <= SCAN; end                        

              5'd5 :  begin y_p <= 8'hb2; x_ph <= 8'h14; x_pl <= 8'h00; num <= 5'd8; char <= "         ";state <= SCAN; end

              5'd6 :  begin y_p <= 8'hb3; x_ph <= 8'h14; x_pl <= 8'h00; num <= 5'd8; char <= "         ";state <= SCAN; end

              5'd7 :  begin y_p <= 8'hb4; x_ph <= 8'h10; x_pl <= 8'h00; num <= 5'd16; char <= "                ";state <= SCAN; end

              5'd8 :  begin y_p <= 8'hb5; x_ph <= 8'h10; x_pl <= 8'h00; num <= 5'd16; char <= "                ";state <= SCAN; end

              5'd9 :  begin y_p <= 8'hb6; x_ph <= 8'h10; x_pl <= 8'h00; num <= 5'd16; char <= "                ";state <= SCAN; end

              5'd10:  begin y_p <= 8'hb7; x_ph <= 8'h10; x_pl <= 8'h00; num <= 5'd16; char <= "                ";state <= SCAN; end              

              5'd11:  ;//begin y_p <= 8'hb1; x_ph <= 8'h15; x_pl <= 8'h00; num <= 5'd 1; char <= sw; state <= SCAN; end

              5'd12:  begin y_p <= 8'hb2; x_ph <= 8'h10; x_pl <= 8'h00; length_hanzi <= 5'd9; 

                    if     (wave==2'b00)begin hanzi_fuzhujishu<=5'd8;state <= CHINESE; end 

                    else if(wave==2'b01)begin hanzi_fuzhujishu<=5'd2;state <= CHINESE; end 

                    else if(wave==2'b10)begin hanzi_fuzhujishu<=5'd4;state <= CHINESE; end 

                    else begin hanzi_fuzhujishu<=5'd6;state <= CHINESE; end //if(wave==2'b11)

                  end

              5'd13 :  begin y_p <= 8'hb3; x_ph <= 8'h10; x_pl <= 8'h00; length_hanzi <= 5'd9; 

                    if     (wave==2'b00)begin hanzi_fuzhujishu<=5'd9;state <= CHINESE; end 

                    else if(wave==2'b01)begin hanzi_fuzhujishu<=5'd3;state <= CHINESE; end 

                    else if(wave==2'b10)begin hanzi_fuzhujishu<=5'd5;state <= CHINESE; end 

                    else begin hanzi_fuzhujishu<=5'd7;state <= CHINESE; end //if(wave==2'b11)

                  end

              5'd14 :  begin y_p <= 8'hb5; x_ph <= 8'h10; x_pl <= 8'h00; num <= 5'd16; char <= char3;state <= SCAN; end

              default: state <= IDLE;

            endcase

          end

        INIT:begin  //鍒濆鍖栫姸鎬

            case(cnt_init)

              5'd0:  begin oled_rst <= LOW; cnt_init <= cnt_init + 1'b1; end  //澶嶄綅鏈夋晥

              5'd1:  begin num_delay <= 16'd25000; state <= DELAY; state_back <= INIT; cnt_init <= cnt_init + 1'b1; end  //寤舵椂澶т簬3us

              5'd2:  begin oled_rst <= HIGH; cnt_init <= cnt_init + 1'b1; end  //澶嶄綅鎭㈠

              5'd3:  begin num_delay <= 16'd25000; state <= DELAY; state_back <= INIT; cnt_init <= cnt_init + 1'b1; end  //寤舵椂澶т簬220us

              5'd4:  begin 

                    if(cnt>=INIT_DEPTH) begin  //褰5鏉℃寚浠ゅ強鏁版嵁鍙戝嚭鍚庯紝閰嶇疆瀹屾垚

                      cnt <= 1'b0;

                      cnt_init <= cnt_init + 1'b1;

                    end else begin  

                      cnt <= cnt + 1'b1; num_delay <= 16'd5;

                      oled_dcn <= CMD; char_reg <= cmd[cnt]; state <= WRITE; state_back <= INIT;

                    end

                  end

              5'd5:  begin cnt_init <= 1'b0; state <= MAIN; end  //鍒濆鍖栧畬鎴愶紝杩斿洖MAIN鐘舵€

              default: state <= IDLE;

            endcase

          end

        SCAN:begin  //鍒峰睆鐘舵€侊紝浠嶳AM涓鍙栨暟鎹埛灞

            if(cnt_scan == 5'd11) begin

              if(num) cnt_scan <= 5'd3;

              else cnt_scan <= cnt_scan + 1'b1;

            end 

            else if(cnt_scan == 5'd12) cnt_scan <= 1'b0;

            else cnt_scan <= cnt_scan + 1'b1;

            case(cnt_scan)

              5'd 0:  begin oled_dcn <= CMD; char_reg <= y_p; state <= WRITE; state_back <= SCAN; end    //瀹氫綅鍒楅〉鍦板潃

              5'd 1:  begin oled_dcn <= CMD; char_reg <= x_pl; state <= WRITE; state_back <= SCAN; end  //瀹氫綅琛屽湴鍧€浣庝綅

              5'd 2:  begin oled_dcn <= CMD; char_reg <= x_ph; state <= WRITE; state_back <= SCAN; end  //瀹氫綅琛屽湴鍧€楂樹綅

              5'd 3:  begin num <= num - 1'b1;end

              5'd 4:  begin oled_dcn <= DATA; char_reg <= 8'h00; state <= WRITE; state_back <= SCAN; end  //灏*8鐐归樀缂栫▼8*8

              5'd 5:  begin oled_dcn <= DATA; char_reg <= 8'h00; state <= WRITE; state_back <= SCAN; end  //灏*8鐐归樀缂栫▼8*8

              5'd 6:  begin oled_dcn <= DATA; char_reg <= 8'h00; state <= WRITE; state_back <= SCAN; end  //灏*8鐐归樀缂栫▼8*8

              5'd 7:  begin oled_dcn <= DATA; char_reg <= mem[char[(num*8)+:8]][39:32]; state <= WRITE; state_back <= SCAN; end

              5'd 8:  begin oled_dcn <= DATA; char_reg <= mem[char[(num*8)+:8]][31:24]; state <= WRITE; state_back <= SCAN; end

              5'd 9:  begin oled_dcn <= DATA; char_reg <= mem[char[(num*8)+:8]][23:16]; state <= WRITE; state_back <= SCAN; end

              5'd10:  begin oled_dcn <= DATA; char_reg <= mem[char[(num*8)+:8]][15: 8]; state <= WRITE; state_back <= SCAN; end

              5'd11:  begin oled_dcn <= DATA; char_reg <= mem[char[(num*8)+:8]][ 7: 0]; state <= WRITE; state_back <= SCAN; end

              5'd12:  begin state <= MAIN; end

              default: state <= IDLE;

            endcase

          end

        //length_hanzi<=5'd8-----涓€琛屽啓鍏

        CHINESE:begin  //鏄剧ず姹夊瓧

            if(cnt_chinese == 5'd11) begin

              if(length_hanzi>=5'd2) cnt_chinese <= 5'd3;

              else cnt_chinese <= cnt_chinese + 1'b1;

            end 

            else if(cnt_chinese == 5'd12) cnt_chinese <= 1'b0;

            else cnt_chinese <= cnt_chinese+1'b1;

            case(cnt_chinese)    

              5'd 0:  begin oled_dcn <= CMD; char_reg <= y_p; state <= WRITE; state_back <= CHINESE; end    //瀹氫綅鍒楅〉鍦板潃

              5'd 1:  begin oled_dcn <= CMD; char_reg <= 8'h00; state <= WRITE; state_back <= CHINESE; end  //瀹氫綅琛屽湴鍧€浣庝綅

              5'd 2:  begin oled_dcn <= CMD; char_reg <= 8'h10; state <= WRITE; state_back <= CHINESE; end  //瀹氫綅琛屽湴鍧€楂樹綅

              5'd 3:  begin length_hanzi <= length_hanzi - 1'b1;end//length_hanzi鍒濆=9锛氭瘡琛岄暱搴︿负8

              5'd 4:  begin oled_dcn <= DATA; char_reg <= mem_hanzi[hanzi_fuzhujishu*8+8-length_hanzi][63:56]; state <= WRITE; state_back <= CHINESE; end

              5'd 5:  begin oled_dcn <= DATA; char_reg <= mem_hanzi[hanzi_fuzhujishu*8+8-length_hanzi][55:48]; state <= WRITE; state_back <= CHINESE; end

              5'd 6:  begin oled_dcn <= DATA; char_reg <= mem_hanzi[hanzi_fuzhujishu*8+8-length_hanzi][47:40]; state <= WRITE; state_back <= CHINESE; end

              5'd 7:  begin oled_dcn <= DATA; char_reg <= mem_hanzi[hanzi_fuzhujishu*8+8-length_hanzi][39:32]; state <= WRITE; state_back <= CHINESE; end

              5'd 8:  begin oled_dcn <= DATA; char_reg <= mem_hanzi[hanzi_fuzhujishu*8+8-length_hanzi][31:24]; state <= WRITE; state_back <= CHINESE; end

              5'd 9:  begin oled_dcn <= DATA; char_reg <= mem_hanzi[hanzi_fuzhujishu*8+8-length_hanzi][23:16]; state <= WRITE; state_back <= CHINESE; end

              5'd10:  begin oled_dcn <= DATA; char_reg <= mem_hanzi[hanzi_fuzhujishu*8+8-length_hanzi][15: 8]; state <= WRITE; state_back <= CHINESE; end

              5'd11:  begin oled_dcn <= DATA; char_reg <= mem_hanzi[hanzi_fuzhujishu*8+8-length_hanzi][ 7: 0]; state <= WRITE; state_back <= CHINESE; end 

              5'd12:  begin state <= MAIN; end

              default: state <= IDLE;

            endcase

          end

        WRITE:begin  //WRITE鐘舵€侊紝灏嗘暟鎹寜鐓PI鏃跺簭鍙戦€佺粰灞忓箷

            if(cnt_write >= 5'd17) cnt_write <= 1'b0;

            else cnt_write <= cnt_write + 1'b1;

            case(cnt_write)

              5'd 0:  begin oled_csn <= LOW; end  //9浣嶆暟鎹渶楂樹綅涓哄懡浠ゆ暟鎹帶鍒朵綅

              5'd 1:  begin oled_clk <= LOW; oled_dat <= char_reg[7]; end  //鍏堝彂楂樹綅鏁版嵁

              5'd 2:  begin oled_clk <= HIGH; end

              5'd 3:  begin oled_clk <= LOW; oled_dat <= char_reg[6]; end

              5'd 4:  begin oled_clk <= HIGH; end

              5'd 5:  begin oled_clk <= LOW; oled_dat <= char_reg[5]; end

              5'd 6:  begin oled_clk <= HIGH; end

              5'd 7:  begin oled_clk <= LOW; oled_dat <= char_reg[4]; end

              5'd 8:  begin oled_clk <= HIGH; end

              5'd 9:  begin oled_clk <= LOW; oled_dat <= char_reg[3]; end

              5'd10:  begin oled_clk <= HIGH; end

              5'd11:  begin oled_clk <= LOW; oled_dat <= char_reg[2]; end

              5'd12:  begin oled_clk <= HIGH; end

              5'd13:  begin oled_clk <= LOW; oled_dat <= char_reg[1]; end

              5'd14:  begin oled_clk <= HIGH; end

              5'd15:  begin oled_clk <= LOW; oled_dat <= char_reg[0]; end  //鍚庡彂浣庝綅鏁版嵁

              5'd16:  begin oled_clk <= HIGH; end

              5'd17:  begin oled_csn <= HIGH; state <= DELAY; end  //

              default: state <= IDLE;

            endcase

          end

        DELAY:begin  //寤舵椂鐘舵€

            if(cnt_delay >= num_delay) begin

              cnt_delay <= 16'd0; state <= state_back; 

            end else cnt_delay <= cnt_delay + 1'b1;

          end

        default:state <= IDLE;

      endcase

    end

  end

  //OLED閰嶇疆鎸囦护鏁版嵁

  always@(posedge rst_n)

    begin

      cmd[0 ] = {8'hae}; 

      cmd[1 ] = {8'hd5}; 

      cmd[2 ] = {8'h80}; 

      cmd[3 ] = {8'ha8}; 

      cmd[4 ] = {8'h3f}; 

      cmd[5 ] = {8'hd3}; 

      cmd[6 ] = {8'h00}; 

      cmd[7 ] = {8'h40}; 

      cmd[8 ] = {8'h8d}; 

      cmd[9 ] = {8'h14}; 

      cmd[10] = {8'h20}; 

      cmd[11] = {8'h02};

      cmd[12] = {8'hc8};

      cmd[13] = {8'ha1};

      cmd[14] = {8'hda};

      cmd[15] = {8'h12};

      cmd[16] = {8'h81};

      cmd[17] = {8'hcf};

      cmd[18] = {8'hd9};

      cmd[19] = {8'hf1};

      cmd[20] = {8'hdb};

      cmd[21] = {8'h40};

      cmd[22] = {8'haf};

    end 

  //5*8鐐归樀瀛楀簱鏁版嵁

  always@(posedge rst_n)

    begin

      mem[  0] = {8'h3E, 8'h51, 8'h49, 8'h45, 8'h3E};   // 48  0

      mem[  1] = {8'h00, 8'h42, 8'h7F, 8'h40, 8'h00};   // 49  1

      mem[  2] = {8'h42, 8'h61, 8'h51, 8'h49, 8'h46};   // 50  2

      mem[  3] = {8'h21, 8'h41, 8'h45, 8'h4B, 8'h31};   // 51  3

      mem[  4] = {8'h18, 8'h14, 8'h12, 8'h7F, 8'h10};   // 52  4

      mem[  5] = {8'h27, 8'h45, 8'h45, 8'h45, 8'h39};   // 53  5

      mem[  6] = {8'h3C, 8'h4A, 8'h49, 8'h49, 8'h30};   // 54  6

      mem[  7] = {8'h01, 8'h71, 8'h09, 8'h05, 8'h03};   // 55  7

      mem[  8] = {8'h36, 8'h49, 8'h49, 8'h49, 8'h36};   // 56  8

      mem[  9] = {8'h06, 8'h49, 8'h49, 8'h29, 8'h1E};   // 57  9

      mem[ 10] = {8'h7C, 8'h12, 8'h11, 8'h12, 8'h7C};   // 65  A

      mem[ 11] = {8'h7F, 8'h49, 8'h49, 8'h49, 8'h36};   // 66  B

      mem[ 12] = {8'h3E, 8'h41, 8'h41, 8'h41, 8'h22};   // 67  C

      mem[ 13] = {8'h7F, 8'h41, 8'h41, 8'h22, 8'h1C};   // 68  D

      mem[ 14] = {8'h7F, 8'h49, 8'h49, 8'h49, 8'h41};   // 69  E

      mem[ 15] = {8'h7F, 8'h09, 8'h09, 8'h09, 8'h01};   // 70  F

      mem[ 32] = {8'h00, 8'h00, 8'h00, 8'h00, 8'h00};   // 32  sp 

      mem[ 33] = {8'h00, 8'h00, 8'h2f, 8'h00, 8'h00};   // 33  !  

      mem[ 34] = {8'h00, 8'h07, 8'h00, 8'h07, 8'h00};   // 34  

      mem[ 35] = {8'h14, 8'h7f, 8'h14, 8'h7f, 8'h14};   // 35  #

      mem[ 36] = {8'h24, 8'h2a, 8'h7f, 8'h2a, 8'h12};   // 36  $

      mem[ 37] = {8'h62, 8'h64, 8'h08, 8'h13, 8'h23};   // 37  %

      mem[ 38] = {8'h36, 8'h49, 8'h55, 8'h22, 8'h50};   // 38  &

      mem[ 39] = {8'h00, 8'h05, 8'h03, 8'h00, 8'h00};   // 39  '

      mem[ 40] = {8'h00, 8'h1c, 8'h22, 8'h41, 8'h00};   // 40  (

      mem[ 41] = {8'h00, 8'h41, 8'h22, 8'h1c, 8'h00};   // 41  )

      mem[ 42] = {8'h14, 8'h08, 8'h3E, 8'h08, 8'h14};   // 42  *

      mem[ 43] = {8'h08, 8'h08, 8'h3E, 8'h08, 8'h08};   // 43  +

      mem[ 44] = {8'h00, 8'h00, 8'hA0, 8'h60, 8'h00};   // 44  ,

      mem[ 45] = {8'h08, 8'h08, 8'h08, 8'h08, 8'h08};   // 45  -

      mem[ 46] = {8'h00, 8'h60, 8'h60, 8'h00, 8'h00};   // 46  .

      mem[ 47] = {8'h20, 8'h10, 8'h08, 8'h04, 8'h02};   // 47  /

      mem[ 48] = {8'h3E, 8'h51, 8'h49, 8'h45, 8'h3E};   // 48  0

      mem[ 49] = {8'h00, 8'h42, 8'h7F, 8'h40, 8'h00};   // 49  1

      mem[ 50] = {8'h42, 8'h61, 8'h51, 8'h49, 8'h46};   // 50  2

      mem[ 51] = {8'h21, 8'h41, 8'h45, 8'h4B, 8'h31};   // 51  3

      mem[ 52] = {8'h18, 8'h14, 8'h12, 8'h7F, 8'h10};   // 52  4

      mem[ 53] = {8'h27, 8'h45, 8'h45, 8'h45, 8'h39};   // 53  5

      mem[ 54] = {8'h3C, 8'h4A, 8'h49, 8'h49