基于Lattice XO2-4000HC的ADC数字电压表及OLED显示设计
基于Lattice XO2-4000HC核心板及电子森林综合训练底板,实现了通过串行ADC对旋转电位计产生的电压进行转换,并将电压值显示在数码管及OLED屏幕上。
标签
FPGA
数字逻辑
接口
葉SiR
更新2021-09-05
1027

一、项目要求

利用ADC制作一个数字电压表

  1. 旋转电位计可以产生0-3.3V的电压;
  2. 利用板上的串行ADC对电压进行转换;
  3. 将电压值在板上的OLED屏幕上显示出来。

二、设计思路及功能模块

本次使用的硬件为基于Lattice XO2-4000HC FPGA核心板及电子森林综合训练底板,开发环境为Diamond 3.10,验证环境为QuestaSim-64 2021.1,使用Verilog编程语言。

本项目分为如下几个功能模块:

  1. ADC驱动及数码管显示模块:adc_driver2seg;
  2. OLED驱动模块:oled_driver_adc;
  3. 项目顶层模块:proj_top。

在ADC驱动及数码管显示模块下分为:

  1. ADC驱动模块;
  2. 数码管显示模块(额外完成);
  3. adc_driver2seg顶层模块。

在OLED驱动模块下分为:

  1. oled_cmd_RAM;
  2. oled_char_RAM;
  3. oled_driver_adc顶层模块。

总系统RTL如下图所示:

项目RTL

三、各模块代码及简要说明

3.1 ADC驱动模块

ADC驱动模块原先参考电子森林:简易电压表设计中关于ADC081S101采样的程序,该程序主要思想是通过计数器编写一个ADC采样时钟(SCLK),根据计数器的值编写对应的操作。而后改为更好的FSM形式的驱动,该驱动的SCLK可以直接使用系统时钟,不必损失转换速度;还可根据实际ADC芯片的工作时序进行状态调整;且可通过adc_eoc信号对片选位作出自动控制,以精确控制ADC的转换流程。

module adc_driver #(
    parameter ADC_WIDTH = 8
)(
    input sys_clk,
    input rst_n,
    input sdo,
    output sclk,
    output reg adc_csn,
    output reg [ADC_WIDTH-1:0] adc_data
);
    localparam IDLE = 2'b00;
    localparam HOLD = 2'b01;
    localparam CONVERT = 2'b10;
    localparam FINISH = 2'b11;

    wire adc_eoc;

    reg [ADC_WIDTH-1:0] adc_data_reg;
    reg [1:0] state;
    reg [1:0] CNT_0;
    reg [2:0] CNT_data;

    assign sclk = sys_clk;
    assign adc_eoc = (state == FINISH);

    always @(posedge sys_clk or negedge rst_n) begin
        if(!rst_n)
            adc_csn <= 1'b1;
        else if(adc_eoc)
            adc_csn <= 1'b1;
        else
            adc_csn <= 1'b0;
    end

    always @(posedge sys_clk or negedge rst_n) begin
        if(!rst_n) begin
            state <= IDLE;
            adc_data <= 8'b0;
            adc_data_reg <= 8'b0;
            CNT_0 <= 2'b10;
            CNT_data <= 3'b111;
        end
        else
            case(state)
                IDLE: begin
                    state <= HOLD;
                end

                HOLD: begin
                    CNT_0 <= CNT_0 - 1'b1;
                    if(CNT_0 == 0) begin
                        state <= CONVERT;
                        CNT_0 <= 2'b10;
                    end         
                    else
                        state <= HOLD;
                end

                CONVERT: begin
                    CNT_data <= CNT_data - 1'b1;
                    adc_data_reg <= {adc_data_reg[ADC_WIDTH-2:0], sdo};
                    if(CNT_data == 0) begin
                        state <= FINISH;
                        CNT_data <= 3'b111;
                    end
                    else
                        state <= CONVERT;
                end

                FINISH: begin
                    adc_data <= adc_data_reg;
                    state <= IDLE;
                end

                default: state <= IDLE;
            endcase
    end

endmodule

3.2 数码管显示模块

3.2.1 二进制转BCD码模块

参考电子森林:简易电压表设计的代码并做了改进,将if判断操作写成function便于多次调用,改进后的代码如下:

module bin2bcd #(
    parameter ADC_WIDTH = 8
)(
    input clk,
    input rst_n,
    input [ADC_WIDTH-1:0] adc_data,
    output reg [19:0] bcd_code
);
    wire [15:0] bin_code;
    reg [35:0] shift_reg;   // 16*2+4=36
    
    assign bin_code = adc_data * 16'd129; // MAX 32895(1000_0000_0111_1111)

    // BCD码各位数据作满5加3操作
    function [3:0] fout(input [3:0] fin);
        fout = (fin > 4) ? (fin + 2'b11) : fin;
    endfunction

    always @(*) begin
        shift_reg = bin_code;
        if(!rst_n)
            bcd_code = 'b0;
        else begin
            repeat(16) begin
                shift_reg[19:16] = fout(shift_reg[19:16]);
                shift_reg[23:20] = fout(shift_reg[23:20]);
                shift_reg[27:24] = fout(shift_reg[27:24]);
                shift_reg[31:28] = fout(shift_reg[31:28]);
                shift_reg[35:32] = fout(shift_reg[35:32]);
			    shift_reg = shift_reg << 1; 
		    end
		    bcd_code = shift_reg[35:16];  
        end
    end

endmodule

3.2.2 数码管驱动模块

参考电子森林:数码管显示代码并做了改进,为提高通用性,将共阴/阳极与小数点亮灭的控制写入模块的参数(parameter)中,本项目采用共阴极数码管。

module seg_display #(
    parameter seg_dig = 1'b0,   // 1'b0:共阴极;1'b1:共阳极
    parameter seg_dp = 1'b1
)(
    input clk,
    input rst_n,
    input [3:0] seg_data,
    output reg [8:0] seg_led    // DIG、DP、G、F、E、D、C、B、A
);
    always @(posedge clk or negedge rst_n) begin
        if(!rst_n)
            seg_led <= 9'h3f;   // 0
        else if(!seg_dig)
            case(seg_data)
                0: seg_led <= {seg_dig, seg_dp, 7'h3f}; // 0
	            1: seg_led <= {seg_dig, seg_dp, 7'h06}; // 1
	            2: seg_led <= {seg_dig, seg_dp, 7'h5b}; // 2
	            3: seg_led <= {seg_dig, seg_dp, 7'h4f}; // 3
	            4: seg_led <= {seg_dig, seg_dp, 7'h66}; // 4
	            5: seg_led <= {seg_dig, seg_dp, 7'h6d}; // 5
	            6: seg_led <= {seg_dig, seg_dp, 7'h7d}; // 6
	            7: seg_led <= {seg_dig, seg_dp, 7'h07}; // 7
	            8: seg_led <= {seg_dig, seg_dp, 7'h7f}; // 8
	            9: seg_led <= {seg_dig, seg_dp, 7'h6f}; // 9
                10:seg_led <= {seg_dig, seg_dp, 7'h77}; // A
                11:seg_led <= {seg_dig, seg_dp, 7'h7c}; // B
                12:seg_led <= {seg_dig, seg_dp, 7'h39}; // C
                13:seg_led <= {seg_dig, seg_dp, 7'h5e}; // D
                14:seg_led <= {seg_dig, seg_dp, 7'h79}; // E
                15:seg_led <= {seg_dig, seg_dp, 7'h71}; // F
                default: seg_led <= {seg_dig, seg_dp, 7'h3f};
            endcase
        else
            case(seg_data)
                0: seg_led <= {seg_dig, seg_dp, 7'hc0}; // 0
	            1: seg_led <= {seg_dig, seg_dp, 7'hf9}; // 1
	            2: seg_led <= {seg_dig, seg_dp, 7'ha4}; // 2
	            3: seg_led <= {seg_dig, seg_dp, 7'hb0}; // 3
	            4: seg_led <= {seg_dig, seg_dp, 7'h99}; // 4
	            5: seg_led <= {seg_dig, seg_dp, 7'h92}; // 5
	            6: seg_led <= {seg_dig, seg_dp, 7'h82}; // 6
	            7: seg_led <= {seg_dig, seg_dp, 7'hf8}; // 7
	            8: seg_led <= {seg_dig, seg_dp, 7'h80}; // 8
	            9: seg_led <= {seg_dig, seg_dp, 7'h90}; // 9
                10:seg_led <= {seg_dig, seg_dp, 7'h88}; // A
                11:seg_led <= {seg_dig, seg_dp, 7'h83}; // B
                12:seg_led <= {seg_dig, seg_dp, 7'hc6}; // C
                13:seg_led <= {seg_dig, seg_dp, 7'ha1}; // D
                14:seg_led <= {seg_dig, seg_dp, 7'h86}; // E
                15:seg_led <= {seg_dig, seg_dp, 7'h8e}; // F
                default: seg_led <= {seg_dig, seg_dp, 7'hc0};
            endcase
    end

endmodule

3.2.3 adc2seg顶层模块

将ADC转换的8位数字量(adc_data)输入bin2bcd模块,输出8位BCD码(bcd_code),高4位输入seg_display模块,并点亮小数点,低4位同理,但不点亮小数点。输出端的8位信号oled_display_digital(即转换后的BCD码,个位+一位小数)输入至OLED显示模块。

module adc2seg #(
    parameter ADC_WIDTH = 8,
    parameter seg_dig = 1'b0,
    parameter seg_dp = 2'b10
)(
    input clk,
    input rst_n,
    input [ADC_WIDTH-1:0] adc_data,
    output [7:0] oled_display_digital,
    output [8:0] digital_1,
    output [8:0] digital_2
);
    wire [19:0] bcd_code;

    bin2bcd #(
        .ADC_WIDTH(ADC_WIDTH)
    ) ins1(
       .clk(clk),
       .rst_n(rst_n),
       .adc_data(adc_data),
       .bcd_code(bcd_code) 
    );

    seg_display #(
        .seg_dig(seg_dig),
        .seg_dp(seg_dp[1])
    ) seg_1(
        .clk(clk),
        .rst_n(rst_n),
        .seg_data(bcd_code[19:16]),
        .seg_led(digital_1)
    );

    seg_display #(
        .seg_dig(seg_dig),
        .seg_dp(seg_dp[0])
    ) seg_2(
        .clk(clk),
        .rst_n(rst_n),
        .seg_data(bcd_code[15:12]),
        .seg_led(digital_2)
    );

    assign oled_display_digital = bcd_code[19:12];

endmodule

3.3 adc_driver2seg顶层模块

顶层模块将前述ADC驱动与数码管显示模块组合即可。为将8位数字量在8个LED上显示,额外增加了一个8位leds信号,对ADC转换后的数据取反后输出即可(1灭0亮)。

module adc_driver2seg #(
    parameter ADC_WIDTH = 8,
    parameter seg_dig = 1'b0,
    parameter seg_dp = 2'b10
)(
    input sys_clk,
    input rst_n,
    input sdo,
    output sclk,
    output adc_csn,
    output [ADC_WIDTH-1:0] leds,
    output [7:0] oled_display_digital,
    output [8:0] digital_1,
    output [8:0] digital_2
);
    wire [ADC_WIDTH-1:0] adc_data;

    assign leds = ~adc_data;

    adc_driver #(
        .ADC_WIDTH(ADC_WIDTH)
    ) adc_driver(
        .sys_clk(sys_clk),
        .rst_n(rst_n),
        .sdo(sdo),
        .sclk(sclk),
        .adc_csn(adc_csn),
        .adc_data(adc_data)
    );

    adc2seg #(
        .ADC_WIDTH(ADC_WIDTH),
        .seg_dig(seg_dig),
        .seg_dp(seg_dp)
    ) segment_displayer(
        .clk(sys_clk),
        .rst_n(rst_n),
        .adc_data(adc_data),
        .oled_display_digital(oled_display_digital),
        .digital_1(digital_1),
        .digital_2(digital_2)
    );

endmodule

3.4 OLED驱动模块

控制OLED需要5个信号:

  1. oled_csn:使能,低有效;
  2. oled_rst:复位,低有效;
  3. oled_dcn:数据/指令控制,高为输入数据,低为输入指令;
  4. oled_clk;
  5. oled_data:数据/指令输入。

参考电子森林:OLED驱动说明及Verilog代码实例的OLED驱动模块并作出改进。首先将整个模块拆分为OLED驱动、命令RAM(oled_cmd_RAM,存放初始化命令,准确的说应该是ROM)、字库RAM(oled_char_RAM,存放显示的字符编码)。

oled_cmd_RAM如下:

module oled_cmd_RAM #(
    parameter RAM_WIDTH = 8,
    parameter RAM_DEPTH = 32,
    parameter ADDR_WIDTH = 5
)(
    input clk,
    input rst_n,
    input re,
    input [ADDR_WIDTH-1:0] addr,
    output reg [RAM_WIDTH-1:0] data
);
    reg [RAM_WIDTH-1:0] Mem[RAM_DEPTH-1:0];

    always @(posedge rst_n) begin
		Mem[ 0] = 8'hae;	// 关闭屏幕
		Mem[ 1] = 8'h81;
		Mem[ 2] = 8'hff;	// 设置对比度:256级
		Mem[ 3] = 8'ha6;	// 设置显示模式为1亮0灭
		Mem[ 4] = 8'h20;
		Mem[ 5] = 8'h02;	// 设置页寻址模式
		Mem[ 6] = 8'h00; 	// 设置起始列地址低位
		Mem[ 7] = 8'h10;	// 设置起始列地址高位
		Mem[ 8] = 8'h40;	// 设置GDDRAM起始行: (01)xxxxxx -> 40为第0行
		Mem[ 9] = 8'ha1;	// 设置COL0映射到SEG0*
		Mem[10] = 8'hc8;	// 设置COM扫描方向:从COM0至COM[N-1]*
		Mem[11] = 8'ha8;
		Mem[12] = 8'h1F; 	// 设置复用率:即选通的COM行数为1F*
		Mem[13] = 8'hd3;
		Mem[14] = 8'h00;	// 设置垂直显示偏移:00~3F
		Mem[15] = 8'hd5;
		Mem[16] = 8'h80;	// 设置显示时钟分频数和fosc
		Mem[17] = 8'hd9;
		Mem[18] = 8'h1f;	// 设置预充电周期
		Mem[19] = 8'hda;
		Mem[20] = 8'h02;	// 设置COM硬件配置:禁止左右反置、使用序列COM引脚配置*
		Mem[21] = 8'hdb;
		Mem[22] = 8'h40;	// 设置VCOMH输出的高电平 
		Mem[23] = 8'h8d;
		Mem[24] = 8'ha4;	// 设置显示模式为正常模式,此时屏幕输出GDDRAM中的显示数据
		Mem[25] = 8'haf;	// 开启屏幕
    end

    always @(posedge clk) begin
        if(!re)
            data <= Mem[addr]; 
        else
            data <= 8'b0;
    end

endmodule

oled_char_RAM如下:

module oled_char_RAM #(
    parameter RAM_WIDTH = 40,
    parameter RAM_DEPTH = 256,
    parameter ADDR_WIDTH = 8
)(
    input clk,
    input rst_n,
    input re,
    input [ADDR_WIDTH-1:0] addr,
    output reg [RAM_WIDTH-1:0] data
);
    reg [RAM_WIDTH-1:0] Mem[RAM_DEPTH-1:0];

    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, 8'h30};   // 54  6
		Mem[ 55] = {8'h01, 8'h71, 8'h09, 8'h05, 8'h03};   // 55  7
		Mem[ 56] = {8'h36, 8'h49, 8'h49, 8'h49, 8'h36};   // 56  8
		Mem[ 57] = {8'h06, 8'h49, 8'h49, 8'h29, 8'h1E};   // 57  9
		Mem[ 58] = {8'h00, 8'h36, 8'h36, 8'h00, 8'h00};   // 58  :
		Mem[ 59] = {8'h00, 8'h56, 8'h36, 8'h00, 8'h00};   // 59  ;
		Mem[ 60] = {8'h08, 8'h14, 8'h22, 8'h41, 8'h00};   // 60  <
		Mem[ 61] = {8'h14, 8'h14, 8'h14, 8'h14, 8'h14};   // 61  =
		Mem[ 62] = {8'h00, 8'h41, 8'h22, 8'h14, 8'h08};   // 62  >
		Mem[ 63] = {8'h02, 8'h01, 8'h51, 8'h09, 8'h06};   // 63  ?
		Mem[ 64] = {8'h32, 8'h49, 8'h59, 8'h51, 8'h3E};   // 64  @
		Mem[ 65] = {8'h7C, 8'h12, 8'h11, 8'h12, 8'h7C};   // 65  A
		Mem[ 66] = {8'h7F, 8'h49, 8'h49, 8'h49, 8'h36};   // 66  B
		Mem[ 67] = {8'h3E, 8'h41, 8'h41, 8'h41, 8'h22};   // 67  C
		Mem[ 68] = {8'h7F, 8'h41, 8'h41, 8'h22, 8'h1C};   // 68  D
		Mem[ 69] = {8'h7F, 8'h49, 8'h49, 8'h49, 8'h41};   // 69  E
		Mem[ 70] = {8'h7F, 8'h09, 8'h09, 8'h09, 8'h01};   // 70  F
		Mem[ 71] = {8'h3E, 8'h41, 8'h49, 8'h49, 8'h7A};   // 71  G
		Mem[ 72] = {8'h7F, 8'h08, 8'h08, 8'h08, 8'h7F};   // 72  H
		Mem[ 73] = {8'h00, 8'h41, 8'h7F, 8'h41, 8'h00};   // 73  I
		Mem[ 74] = {8'h20, 8'h40, 8'h41, 8'h3F, 8'h01};   // 74  J
		Mem[ 75] = {8'h7F, 8'h08, 8'h14, 8'h22, 8'h41};   // 75  K
		Mem[ 76] = {8'h7F, 8'h40, 8'h40, 8'h40, 8'h40};   // 76  L
		Mem[ 77] = {8'h7F, 8'h02, 8'h0C, 8'h02, 8'h7F};   // 77  M
		Mem[ 78] = {8'h7F, 8'h04, 8'h08, 8'h10, 8'h7F};   // 78  N
		Mem[ 79] = {8'h3E, 8'h41, 8'h41, 8'h41, 8'h3E};   // 79  O
		Mem[ 80] = {8'h7F, 8'h09, 8'h09, 8'h09, 8'h06};   // 80  P
		Mem[ 81] = {8'h3E, 8'h41, 8'h51, 8'h21, 8'h5E};   // 81  Q
		Mem[ 82] = {8'h7F, 8'h09, 8'h19, 8'h29, 8'h46};   // 82  R
		Mem[ 83] = {8'h46, 8'h49, 8'h49, 8'h49, 8'h31};   // 83  S
		Mem[ 84] = {8'h01, 8'h01, 8'h7F, 8'h01, 8'h01};   // 84  T
        Mem[ 85] = {8'h3F, 8'h40, 8'h40, 8'h40, 8'h3F};   // 85  U
        Mem[ 86] = {8'h1F, 8'h20, 8'h40, 8'h20, 8'h1F};   // 86  V
        Mem[ 87] = {8'h3F, 8'h40, 8'h38, 8'h40, 8'h3F};   // 87  W
        Mem[ 88] = {8'h63, 8'h14, 8'h08, 8'h14, 8'h63};   // 88  X
        Mem[ 89] = {8'h07, 8'h08, 8'h70, 8'h08, 8'h07};   // 89  Y
        Mem[ 90] = {8'h61, 8'h51, 8'h49, 8'h45, 8'h43};   // 90  Z
        Mem[ 91] = {8'h00, 8'h7F, 8'h41, 8'h41, 8'h00};   // 91  [
        Mem[ 92] = {8'h55, 8'h2A, 8'h55, 8'h2A, 8'h55};   // 92  .
        Mem[ 93] = {8'h00, 8'h41, 8'h41, 8'h7F, 8'h00};   // 93  ]
        Mem[ 94] = {8'h04, 8'h02, 8'h01, 8'h02, 8'h04};   // 94  ^
        Mem[ 95] = {8'h40, 8'h40, 8'h40, 8'h40, 8'h40};   // 95  _
        Mem[ 96] = {8'h00, 8'h01, 8'h02, 8'h04, 8'h00};   // 96  '
        Mem[ 97] = {8'h20, 8'h54, 8'h54, 8'h54, 8'h78};   // 97  a
        Mem[ 98] = {8'h7F, 8'h48, 8'h44, 8'h44, 8'h38};   // 98  b
        Mem[ 99] = {8'h38, 8'h44, 8'h44, 8'h44, 8'h20};   // 99  c
        Mem[100] = {8'h38, 8'h44, 8'h44, 8'h48, 8'h7F};   // 100 d
        Mem[101] = {8'h38, 8'h54, 8'h54, 8'h54, 8'h18};   // 101 e
        Mem[102] = {8'h08, 8'h7E, 8'h09, 8'h01, 8'h02};   // 102 f
        Mem[103] = {8'h18, 8'hA4, 8'hA4, 8'hA4, 8'h7C};   // 103 g
        Mem[104] = {8'h7F, 8'h08, 8'h04, 8'h04, 8'h78};   // 104 h
        Mem[105] = {8'h00, 8'h44, 8'h7D, 8'h40, 8'h00};   // 105 i
        Mem[106] = {8'h40, 8'h80, 8'h84, 8'h7D, 8'h00};   // 106 j
        Mem[107] = {8'h7F, 8'h10, 8'h28, 8'h44, 8'h00};   // 107 k
        Mem[108] = {8'h00, 8'h41, 8'h7F, 8'h40, 8'h00};   // 108 l
        Mem[109] = {8'h7C, 8'h04, 8'h18, 8'h04, 8'h78};   // 109 m
        Mem[110] = {8'h7C, 8'h08, 8'h04, 8'h04, 8'h78};   // 110 n
        Mem[111] = {8'h38, 8'h44, 8'h44, 8'h44, 8'h38};   // 111 o
        Mem[112] = {8'hFC, 8'h24, 8'h24, 8'h24, 8'h18};   // 112 p
        Mem[113] = {8'h18, 8'h24, 8'h24, 8'h18, 8'hFC};   // 113 q
        Mem[114] = {8'h7C, 8'h08, 8'h04, 8'h04, 8'h08};   // 114 r
        Mem[115] = {8'h48, 8'h54, 8'h54, 8'h54, 8'h20};   // 115 s
        Mem[116] = {8'h04, 8'h3F, 8'h44, 8'h40, 8'h20};   // 116 t
        Mem[117] = {8'h3C, 8'h40, 8'h40, 8'h20, 8'h7C};   // 117 u
        Mem[118] = {8'h1C, 8'h20, 8'h40, 8'h20, 8'h1C};   // 118 v
        Mem[119] = {8'h3C, 8'h40, 8'h30, 8'h40, 8'h3C};   // 119 w
        Mem[120] = {8'h44, 8'h28, 8'h10, 8'h28, 8'h44};   // 120 x
        Mem[121] = {8'h1C, 8'hA0, 8'hA0, 8'hA0, 8'h7C};   // 121 y
        Mem[122] = {8'h44, 8'h64, 8'h54, 8'h4C, 8'h44};   // 122 z
    end

    always @(posedge clk) begin
        if(re)
            data <= Mem[addr]; 
        else
            data <= 40'b0;
    end

endmodule

之后,oled_driver_adc顶层模块则参考原程序的FSM,并修改要显示的字符串及数据(包括出现的位置):

module oled_driver_adc #(
	parameter CMD_WIDTH = 8,   		// LCD命令宽度
    parameter CMD_DEPTH = 5'd25, 	// LCD初始化的命令的数量
    parameter CHAR_WIDTH = 40,		// 一个文字的数据宽度
    parameter CHAR_DEPTH = 7'd123	// 文字库数量
)(
	input sys_clk,
	input rst_n,
	input [7:0] oled_display_digital, 	// 两位ADC数据(一位小数)
	output reg oled_csn,	//OLCD液晶屏使能
	output reg oled_rst,	//OLCD液晶屏复位
	output reg oled_dcn,	//OLCD数据指令控制
	output reg oled_clk,	//OLCD时钟信号
	output reg oled_data	//OLCD数据信号
);
	localparam IDLE = 3'b0, MAIN = 3'b1, INIT = 3'b10;
	localparam SCAN = 3'b11, WRITE = 3'b100, DELAY = 3'b101;
	localparam HIGH	= 1'b1, LOW = 1'b0;
	localparam DATA	= 1'b1, CMD = 1'b0;

    wire [CMD_WIDTH-1:0]  cmd_out;   // cmd_RAM输出的8位命令
    wire [CHAR_WIDTH-1:0] char_out;   // data_RAM输出的40位文字

    reg [7:0] wr_reg;
	reg	[7:0] ypage, xpage_high, xpage_low;   // 位置
	reg	[(8*21-1):0] char;  // 字符串
	reg	[4:0] char_num;   // 文字个数 最多16
    reg [4:0] cmd_addr;
    reg [7:0] char_addr;
	reg	[2:0] cnt_main;
	reg [2:0] cnt_init;
	reg [3:0] cnt_scan;
	reg	[4:0] cnt_write;
	reg	[14:0]num_delay, cnt_delay;
	reg	[2:0] state, state_last;

    oled_cmd_RAM #(
        .RAM_WIDTH(CMD_WIDTH),
        .RAM_DEPTH(CMD_DEPTH),
        .ADDR_WIDTH(5)
    ) CMD_RAM(
        .clk(sys_clk),
        .rst_n(rst_n),
        .re(oled_dcn),
        .addr(cmd_addr),
        .data(cmd_out)
    );

    oled_char_RAM #(
        .RAM_WIDTH(CHAR_WIDTH),
        .RAM_DEPTH(CHAR_DEPTH),
        .ADDR_WIDTH(8)
    ) CHAR_RAM(
        .clk(sys_clk),
        .rst_n(rst_n),
        .re(oled_dcn),
        .addr(char_addr),
        .data(char_out)
    );
 
	always @(posedge sys_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;
			wr_reg <= 1'b0;
			ypage <= 1'b0;
            xpage_high <= 1'b0; 
            xpage_low <= 1'b0;
			char <= 1'b0; 
			char_num <= 1'b0;
            cmd_addr <= 1'b0;
            char_addr <= 1'b0;
			num_delay <= 15'd5; 
            cnt_delay <= 1'b0; 
			oled_csn <= HIGH; 
            oled_rst <= HIGH; 
            oled_dcn <= CMD; 
            oled_clk <= HIGH; 
            oled_data <= LOW;
			state <= IDLE; 
            state_last <= IDLE;
		end 
        else begin
			case(state)
				IDLE: begin
					cnt_main <= 1'b0; 
					cnt_init <= 1'b0; 
					cnt_scan <= 1'b0; 
					cnt_write <= 1'b0;
					wr_reg <= 1'b0;
					ypage <= 1'b0;
					xpage_high <= 1'b0; 
					xpage_low <= 1'b0;
					char <= 1'b0; 
					char_num <= 1'b0; 
					cmd_addr <= 1'b0;
					char_addr <= 1'b0;
					num_delay <= 15'd5; 
					cnt_delay <= 1'b0; 
					oled_csn <= HIGH; 
					oled_rst <= HIGH; 
					oled_dcn <= CMD; 
					oled_clk <= HIGH; 
					oled_data <= LOW;
					state <= MAIN; 
					state_last <= MAIN;
				end

				MAIN: begin
					if(cnt_main >= 3'd6)
						cnt_main <= 3'd5;
					else 
						cnt_main <= cnt_main + 1'b1;
					case(cnt_main)	//MAIN状态
						3'd0: begin state <= INIT; end
						3'd1: begin ypage <= 8'hb0; xpage_high <= 8'h10; xpage_low <= 8'h00; char_num <= 5'd16; char <= "ADC DATA DISPLAY";state <= SCAN; end
						3'd2: begin ypage <= 8'hb1; xpage_high <= 8'h10; xpage_low <= 8'h00; char_num <= 5'd16; char <= "VOLTAGE:   . V  ";state <= SCAN; end
						3'd3: begin ypage <= 8'hb2; xpage_high <= 8'h10; xpage_low <= 8'h00; char_num <= 5'd16; char <= "AUTHOR: ZIRU PAN";state <= SCAN; end
						3'd4: begin ypage <= 8'hb3; xpage_high <= 8'h10; xpage_low <= 8'h00; char_num <= 5'd16; char <= "                ";state <= SCAN; end
						3'd5: begin ypage <= 8'hb1; xpage_high <= 8'h15; xpage_low <= 8'h00; char_num <= 5'd1 ; char <= oled_display_digital[7:4]; state <= SCAN; end
						3'd6: begin ypage <= 8'hb1; xpage_high <= 8'h16; xpage_low <= 8'h00; char_num <= 5'd1 ; char <= oled_display_digital[3:0]; 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 <= 15'd25000; 
								state <= DELAY; 
								state_last <= 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 <= 15'd25000; 
								state <= DELAY; 
								state_last <= INIT; 
								cnt_init <= cnt_init + 1'b1; 
							end	//延时大于220us
						5'd4: begin 
								if(cmd_addr >= CMD_DEPTH) begin
									cmd_addr <= 1'b0;
									cnt_init <= cnt_init + 1'b1;
								end 
								else begin	
									cmd_addr <= cmd_addr + 1'b1; 
									num_delay <= 15'd5;
									oled_dcn <= CMD; 
									wr_reg <= cmd_out;
									state <= WRITE; 
									state_last <= INIT;
								end
							end
						5'd5: begin 
							cnt_init <= 1'b0; 
							state <= MAIN; 
						end
						default: state <= IDLE;
					endcase
				end
				
				SCAN: begin
					if(cnt_scan == 4'd12) begin
						if(char_num) 
							cnt_scan <= 4'd3;
						else 
							cnt_scan <= cnt_scan + 1'b1;
					end 
					else if(cnt_scan == 4'd13) 
						cnt_scan <= 4'd0;
					else 
						cnt_scan <= cnt_scan + 1'b1;
					case(cnt_scan)
						4'd0: begin oled_dcn <= CMD; wr_reg <= ypage; state <= WRITE; state_last <= SCAN; end    	//定位列页地址
						4'd1: begin oled_dcn <= CMD; wr_reg <= xpage_low; state <= WRITE; state_last <= SCAN; end	//定位行地址低位
						4'd2: begin oled_dcn <= CMD; wr_reg <= xpage_high; state <= WRITE; state_last <= SCAN; end	//定位行地址高位
						4'd3: begin char_num <= char_num - 1'b1; end
						4'd4: begin char_addr <= char[(char_num*8)+:8]; end
						4'd5: begin oled_dcn <= DATA; wr_reg <= 8'h00; state <= WRITE; state_last <= SCAN; end	//5*8点阵变成8*8
						4'd6: begin oled_dcn <= DATA; wr_reg <= 8'h00; state <= WRITE; state_last <= SCAN; end	//5*8点阵变成8*8
						4'd7: begin oled_dcn <= DATA; wr_reg <= 8'h00; state <= WRITE; state_last <= SCAN; end	//5*8点阵变成8*8
						4'd8: begin oled_dcn <= DATA; wr_reg <= char_out[39:32]; state <= WRITE; state_last <= SCAN; end
						4'd9: begin oled_dcn <= DATA; wr_reg <= char_out[31:24]; state <= WRITE; state_last <= SCAN; end
						4'd10:begin oled_dcn <= DATA; wr_reg <= char_out[23:16]; state <= WRITE; state_last <= SCAN; end
						4'd11:begin oled_dcn <= DATA; wr_reg <= char_out[15:8] ; state <= WRITE; state_last <= SCAN; end
						4'd12:begin oled_dcn <= DATA; wr_reg <= char_out[7:0]  ; state <= WRITE; state_last <= SCAN; end
						4'd13:begin state <= MAIN; end
						default: state <= IDLE;
					endcase
				end

				WRITE: begin	//WRITE状态,将数据按照SPI时序发送给屏幕
					if(cnt_write >= 5'd17) 
						cnt_write <= 5'd0;
					else 
						cnt_write <= cnt_write + 1'b1;
					case(cnt_write)
						5'd0: begin oled_csn <= LOW; end	//9位数据最高位为命令数据控制位
						5'd1: begin oled_clk <= LOW; oled_data <= wr_reg[7]; end	//先发高位数据
						5'd2: begin oled_clk <= HIGH; end
						5'd3: begin oled_clk <= LOW; oled_data <= wr_reg[6]; end
						5'd4: begin oled_clk <= HIGH; end
						5'd5: begin oled_clk <= LOW; oled_data <= wr_reg[5]; end
						5'd6: begin oled_clk <= HIGH; end
						5'd7: begin oled_clk <= LOW; oled_data <= wr_reg[4]; end
						5'd8: begin oled_clk <= HIGH; end
						5'd9: begin oled_clk <= LOW; oled_data <= wr_reg[3]; end
						5'd10:begin oled_clk <= HIGH; end
						5'd11:begin oled_clk <= LOW; oled_data <= wr_reg[2]; end
						5'd12:begin oled_clk <= HIGH; end
						5'd13:begin oled_clk <= LOW; oled_data <= wr_reg[1]; end
						5'd14:begin oled_clk <= HIGH; end
						5'd15:begin oled_clk <= LOW; oled_data <= wr_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 <= 15'd0; 
						state <= state_last; 
					end 
					else 
						cnt_delay <= cnt_delay + 1'b1;
				end

				default: state <= IDLE;
			endcase
		end
	end
endmodule

3.5 项目顶层模块proj_top

顶层模块将上述adc_driver2seg模块、oled_driver_adc驱动组合,预留了7个参数。代码如下:

module proj_top #(
    parameter ADC_WIDTH = 8,
    parameter seg_dig = 1'b0,
    parameter seg_dp = 2'b10,
    parameter CMD_WIDTH = 8,   		// LCD命令宽度
    parameter CMD_DEPTH = 5'd26, 	// LCD初始化的命令的数量
    parameter CHAR_WIDTH = 40,		// 一个文字的数据宽度
    parameter CHAR_DEPTH = 7'd123	// 文字库数量
)(
    input sys_clk,
    input rst_n,
    input sdo,
    input sw,
    output sclk,
    output adc_csn,
    output [ADC_WIDTH-1:0] leds,    // LED灯
    output [8:0] digital_1,         // 数码管个位
    output [8:0] digital_2,         // 数码管小数位
    output oled_csn,	            //OLCD液晶屏使能
	output oled_rst,	            //OLCD液晶屏复位
	output oled_dcn,	            //OLCD数据指令控制
	output oled_clk,	            //OLCD时钟信号
	output oled_data,	            //OLCD数据信号
    output [2:0] RGB_led_1,         //RGB三色灯
    output [2:0] RGB_led_2          //RGB三色灯
);
    wire [7:0] oled_display_digital;

    assign RGB_led_1 = {3{~sw}};        //RGB三色灯灭
    assign RGB_led_2 = {3{~sw}};        //RGB三色灯灭

    adc_driver2seg #(
        .ADC_WIDTH(ADC_WIDTH),
        .seg_dig(seg_dig),
        .seg_dp(seg_dp)
    ) adc_driver2seg(
        .sys_clk(sys_clk),
        .rst_n(rst_n),
        .sdo(sdo),
        .sclk(sclk),
        .adc_csn(adc_csn),
        .leds(leds),
        .oled_display_digital(oled_display_digital),
        .digital_1(digital_1),
        .digital_2(digital_2)
    );

    oled_driver_adc #(
        .CMD_WIDTH(CMD_WIDTH),
        .CMD_DEPTH(CMD_DEPTH),
        .CHAR_WIDTH(CHAR_WIDTH),
        .CHAR_DEPTH(CHAR_DEPTH)
    ) oled_driver_adc(
        .sys_clk(sys_clk),
	    .rst_n(rst_n),
	    .oled_display_digital(oled_display_digital),
	    .oled_csn(oled_csn),
	    .oled_rst(oled_rst),
	    .oled_dcn(oled_dcn),
	    .oled_clk(oled_clk),
	    .oled_data(oled_data)
    );

endmodule

注:不知为何,不对三色RGB灯进行任何连接在实际调试中两个灯却常亮,因此代码中改为用一个常闭开关控制两个三色RGB灯熄灭。

四、资源使用情况

Fkes-CaA67wnHXr_nERrrCr65lgD

五、项目总结

  1. 所要求的功能均能正常实现;整个项目工程按功能模块编写,清晰明了;
  2. 不仅完成了项目的所有要求,还实现了将电压值显示在数码管及8个LED灯上;
  3. 经过这次项目的锻炼,我对通过FPGA驱动ADC这种外设有了深入了解,对于OLED屏幕(及SSD1306)的驱动有了较为细致的分析和研究,这对于我的FPGA开发十分有益。

六、更多说明

  1. 详细的项目说明参见基于Lattice XO2-4000HC FPGA核心板及电子森林综合训练底板的ADC数字电压表及OLED显示设计(Verilog)
  2. 关于本项目中ADC驱动模块的说明参见基于Lattice XO2-4000HC FPGA核心板ADS7868驱动模块及波形分析(Verilog)
  3. 关于本项目中OLED驱动模块及工作方式(结合代码)参见基于Lattice XO2-4000HC FPGA核心板的SSD1306 OLED12832驱动芯片指令及工作方式详述(Verilog)
  4. 源代码及.JED文件参见附件压缩包,包括一份文件说明README;
  5. 作者的CSDN主页,欢迎访问。
附件下载
项目源代码.rar
项目源代码,包含各模块代码、testbench及可验证的二进制文件.JED;内含文件说明(README)
ADC_SPI_Proj_proj_top.jed
项目的可验证的二进制文件
团队介绍
北京交通大学 电子信息工程学院 电子科学与技术专业就读
团队成员
潘孜儒
二次元の开发者
评论
0 / 100
查看更多
目录
硬禾服务号
关注最新动态
0512-67862536
info@eetree.cn
江苏省苏州市苏州工业园区新平街388号腾飞创新园A2幢815室
苏州硬禾信息科技有限公司
Copyright © 2023 苏州硬禾信息科技有限公司 All Rights Reserved 苏ICP备19040198号