2021年“暑期一起练”——利用ADC制作一个数字电压表
该项目基于小脚丫FPGA的综合技能训练板,利用ADC制作一个数字电压表,具体功能如下:将电压值在板上的OLED屏幕上显示出来,通过按键可以锁定电压值,相当于万用表上面端的HOLD功能。
标签
FPGA
测试
显示
十年风雪
更新2021-09-05
1124

一、项目需求:

       该项目基于小脚丫FPGA的综合技能训练板,利用ADC制作一个数字电压表,具体功能如下:

1、旋转电位计可以产生0-3.3V的电压;

2、利用板上的串行ADC对电压进行转换;

3、将电压值在板上的OLED屏幕上显示出来;

4、通过按键可以锁定电压值,相当于万用表上面端的HOLD功能;

 

二、设计思路:

       小脚丫FPGA的综合技能训练板上带有一颗SPI接口的串行ADC,可以采集电位计上的电压。板上的ADC是串行ADC,采样率最高为280ksps,可以对频率为20KHz以内的信号(音频信号的范围)进行采样;同时,板上采用了一块128*32分辨率的OLED作为信息显示终端,可以将ADC采集到的电压值显示在屏幕上。板上的OLED和ADC器件都使用SPI通信,因此该系统实现的核心在于解决SPI通信。

       总体设计模块如下图1所示:

Fvp7AXLO1kRDjFJv4YaIf4nFm1Mt

图1 总体设计模块图

三、模块实现:

本系统由ADC驱动模块、OLED驱动模块、ADC电压计算模块、功能按键实现模块和顶层总体逻辑控制模块,分别如下所述:

1、ADC驱动模块:

小脚丫FPGA的综合技能训练板上所带的ADC是一颗BB公司生产的8bit-串行ADC,该ADC最大可以达到280KSPS采样率,该ADC框图如图2所示:

FvM2UF0YFOpVMJen1yARRwiSqHKO

图2 ADS7868框图

       根据ADS7868手册提供的时序图可知该ADC的驱动SPI基本符合标准SPI通信,如图3所示:

FsZtBaQW9aJTnwLkLk5gwqMhu6Z2

图3 ADS7868时序图

       根据该时序图,设计ADC的驱动状态机如图4所示:

FhTPhRNUACRySIS6xb9zS1vurUyr

图4 ADC驱动状态机

       根据该状态机的状态转移设置,且由于输入时钟较快,因此采用分频代码对输入时钟进行8分频,使得ADS7868的驱动时钟符合datasheet中的时钟要求(<6.7us,即最大频率不超过149KHz)编写代码如下所示:

/*分频产生fclk*/
always @(posedge I_sys_clk or negedge I_rst_n) begin
    if(~I_rst_n) begin
        f_sck <= 0;
    end
    else begin
        if(R_cnt == PERIOD-1) begin
            f_sck <= ~f_sck;
            R_cnt <= 0;
        end
        else begin
            R_cnt <= R_cnt + 1;
        end
    end
end 

/*采样状态机*/

always @(posedge I_sys_clk or negedge I_rst_n)
	if(!I_rst_n) begin
		O_adc_cs <= 1'b1; O_adc_clk <= 1'b1;
        O_data <=0;
        O_done <=1'b0;
        bit_cnt <= 4'd8;
        nop_cnt <= 4'd0;
        R_state <= 4'd0;
	end 
    else case(R_state)
		8'd0 :  begin 
                    O_adc_cs <= 1'b1; 
                    O_adc_clk <= 1'b1; 
                    R_state <= 4'd1;
                    bit_cnt <= 4'd8;
                    nop_cnt <= 4'd0;
                end
		8'd1 :  begin
                    O_done <= 1'b0;
                    if(nop_cnt == 4'd4) begin
                        R_state <= 4'd3;
                    end
                    else begin
                        O_adc_cs <= 1'b0;  
                        O_adc_clk <= 1'b1; 
                        R_state <= 4'd2;
                    end
                end
		8'd2 :	begin 
                    O_adc_cs <= 1'b0;  
                    O_adc_clk <= 1'b0;
                    nop_cnt <= nop_cnt + 4'd1;
                    R_state <= 4'd1;
                end
		8'd3 :  begin 
                    if(bit_cnt == 0) begin
                        R_state <= 4'd5;
                    end
                    else begin
                        O_adc_cs <= 1'b0;  
                        O_adc_clk <= 1'b1; 
                        O_data[bit_cnt-1] <= I_adc_data;
                        R_state <= 4'd4;
                    end
                    
                    
                end //4
        8'd4 :	begin
                    O_adc_cs <= 1'b0;  
                    O_adc_clk <= 1'b0; 
                    bit_cnt <= bit_cnt - 4'd1;
                    R_state <= 4'd3;
                end
        8'd5 :	begin
                    O_adc_cs <= 1'b1;  
                    O_adc_clk <= 1'b0;
                    O_done <= 1'b1;
                    R_state <= 4'd0;
                end
	default : begin 
                    O_adc_cs <= 1'b1;  
                    O_adc_clk <= 1'b1;  
                    O_done <= 1'b0;
                end
	endcase

2、OLED驱动模块:

OLED使用FPGA驱动较为复杂,该128X32的点阵屏幕采用SSD1306,虽然SSD1306有多种驱动方式,但是在板子上该驱动方式仅为SPI驱动实现,4-wire SPI总线驱动时序图如图5所示,图6显示为SDD1306刷新RAM时地址与数据的分配关系:

FtVHYp8-gs-Q4ttDLxeO5-NTH9Gq

图5 4-wire SPI驱动时序图

Fi6G53SNEgU-a8YHv45UW3WBcKKd

图6 地址与数据位分配关系

       由于能力有限,且为了加快设计进度,在电子森林上找到了相关的驱动程序(项目地址见【https://www.eetree.cn/wiki/oled_spi_verilog】),但是该驱动程序是直接驱动方式,数据和位置都写死在程序中,可移植性和灵活性较差,因此,对该驱动程序做适当修改,将其改为RAM读写的驱动端口,这样,就可以在RAM的另一个写端口将要显示的内容按地址写入,随后,OLED驱动程序在RAM的读端口按地址读取要显示的数据。修改接口程序如下,提供ram的读地址和读信号:

case(cnt_main)	//MAIN状态
							5'd0:	begin state <= INIT; end
							5'd1:begin
								ram_addr <= 4'd1;
							end
							5'd2:	begin  y_p <= 8'hb0; x_ph <= 8'h10; x_pl <= 8'h00; num <= 5'd16; char <= ram_data[16*8-1:0];state <= SCAN; end
							5'd3: begin
								ram_addr <= 4'd2;
							end
							5'd4:	begin  y_p <= 8'hb1; x_ph <= 8'h10; x_pl <= 8'h00; num <= 5'd16; char <= ram_data[16*8-1:0];state <= SCAN; end
							5'd5:begin
								ram_addr <= 4'd3;
							end
							5'd6:	begin  y_p <= 8'hb2; x_ph <= 8'h10; x_pl <= 8'h00; num <= 5'd16; char <= ram_data[16*8-1:0];state <= SCAN; end
							5'd7:begin
								ram_addr <= 4'd0;
							end
							5'd8:	begin  y_p <= 8'hb3; x_ph <= 8'h10; x_pl <= 8'h00; num <= 5'd16; char <= ram_data[16*8-1:0];state <= SCAN; end
							// 5'd1:	begin y_p <= 8'hb0; x_ph <= 8'h10; x_pl <= 8'h00; num <= 5'd16; char <= "OLED TEST       ";state <= SCAN; end
							// 5'd2:	begin y_p <= 8'hb1; x_ph <= 8'h10; x_pl <= 8'h00; num <= 5'd16; char <= "OLED TEST       ";state <= SCAN; end
							// 5'd3:	begin y_p <= 8'hb2; x_ph <= 8'h10; x_pl <= 8'h00; num <= 5'd16; char <= "OLED TEST       ";state <= SCAN; end
							// 5'd4:	begin y_p <= 8'hb3; x_ph <= 8'h10; x_pl <= 8'h00; num <= 5'd16; char <= "OLED TEST       ";state <= SCAN; end

							// 5'd5:	begin y_p <= 8'hb0; x_ph <= 8'h15; x_pl <= 8'h00; num <= 5'd 1; char <= sw; state <= SCAN; end
							// 5'd6:	begin y_p <= 8'hb1; x_ph <= 8'h15; x_pl <= 8'h00; num <= 5'd 1; char <= sw; state <= SCAN; end
							// 5'd7:	begin y_p <= 8'hb2; x_ph <= 8'h15; x_pl <= 8'h00; num <= 5'd 1; char <= sw; state <= SCAN; end
							// 5'd8:	begin y_p <= 8'hb3; x_ph <= 8'h15; x_pl <= 8'h00; num <= 5'd 1; char <= sw; state <= SCAN; end

							default: state <= IDLE;
						endcase

最后,修改后的的OLED程序部分如下所示,RAM的配置的log如图7所示:

GRAM_MY inst0_0 (
    .WrAddress( WrAddress), 
    .RdAddress( RdAddress), 
    .Data(Data ), 
    .WE(1'b1 ), 
    .RdClock(I_sys_clk ), 
    .RdClockEn( 1'b1), 
    .Reset(!I_rst_n ), 
    .WrClock(I_sys_clk ), 
    .WrClockEn(1'b1 ), 
    .Q( ram_data)
    );

FiIhEKBD_8DrsJXKjssDIeBbMVhh

图7 OLED的RAM配置log

3、ADC电压计算模块:

       由于ADC采集后的结果为8bit的二进制数,如果要显示在OLED上,需要将该二进制数使用如下公式1计算最后得到的电压值,然后采用移位法将获得电压值转换为BCD数,并分为小数和整数显示:

              ADC_Volt = 3.3X(CODE /255)                    (公式1)

       设定ADC电压的显示为5个部分,第一部分为整数部分,第二部分为小数点,第三到第五部分为小数部分,代码如下所示:

ADS7868_DRIVER inst0(
        .I_sys_clk(f_sck),
        .I_rst_n(I_rst_n),
        
        .I_adc_data(I_data),
        .O_adc_clk(O_adc_clk),
        .O_adc_cs(O_adc_cs),

        .O_done(O_led),
        .O_data(O_data)
);

wire [15:0] bin_code = O_data * 16'd129 + 16'd010;
reg	[35:0]	shift_reg; 
//reg [19:0] bcd_code;
always@(bin_code or I_rst_n)begin
	shift_reg = {20'h0,bin_code};
	if(!I_rst_n) bcd_code = 0; 
	else begin 
		repeat(16) begin //循环16次  
			//BCD码各位数据作满5加3操作,
			if (shift_reg[19:16] >= 5) shift_reg[19:16] = shift_reg[19:16] + 2'b11;
			if (shift_reg[23:20] >= 5) shift_reg[23:20] = shift_reg[23:20] + 2'b11;
			if (shift_reg[27:24] >= 5) shift_reg[27:24] = shift_reg[27:24] + 2'b11;
			if (shift_reg[31:28] >= 5) shift_reg[31:28] = shift_reg[31:28] + 2'b11;
			if (shift_reg[35:32] >= 5) shift_reg[35:32] = shift_reg[35:32] + 2'b11;
			shift_reg = shift_reg << 1; 
		end
		bcd_code = shift_reg[35:16];   
	end  
end


/*BCD数拆分为一位整数,三位小数,用于OLED显示*/
assign new_bcd[39:32]  = {4'b0,bcd_code[19:16]};
assign new_bcd[31:24]  = 8'd46;
assign new_bcd[23:16]  = {4'b0,bcd_code[15:12]};
assign new_bcd[15:8]  = {4'b0,bcd_code[11:8]};
assign new_bcd[7:0]  = {4'b0,bcd_code[7:4]};

4、功能按键实现模块:

       为了便于后面实现电压测量过程中方便记录电压,因此增加了一个额外功能:电压显示锁,该功能就是在按一次按键后,OLED 会显示“LOCKED”字样,此时OLED 的电压会锁定在按键前的一刻的采样电压值。

       为了保证按键都是每次按下有效,因此采用边沿检测的方法,每当检测到按键上升沿的同时,根据前一状态判断是锁定电压值还是解锁电压值,代码如下所示:

/*选择输出*/
assign new_bcd_t = (key_scan == 0) ? new_bcd_t : new_bcd; 


/*按键上升沿检测*/
reg [1:0] key_buf;
wire W_key_pos;

always@(posedge I_sys_clk or negedge I_rst_n)
begin
    if(~I_rst_n) begin
        key_buf <= 0;
    end
    else begin
        key_buf <= {key_buf[0],I_key_lock};
    end 
end

assign W_key_pos = (key_buf == 2'b01) ? 1'b1 : 1'b0;



always @(posedge I_sys_clk or negedge I_rst_n) begin
    if(~I_rst_n) begin
        key_scan <= 0;
    end
    else begin
        if(W_key_pos) begin
            key_scan <= ~key_scan; 
        end
        else begin
            key_scan <= key_scan;
        end
    end
end

5、顶层总体逻辑控制模块:

       顶层控制逻辑负责控制整体顺序,并负责传递各个模块的数据,最主要的就是负责把ADC计算好的电压BCD码组装到显示RAM中,并且OLED从RAM中取出显示数据,并显示在OLED上面,代码如下所示:

reg [3:0] R_mainstate;

always @(posedge I_sys_clk or negedge I_rst_n) begin
    if(~I_rst_n) begin
        WrAddress <= 4'd0;
        Data <= {"    VOLTMETER   "};
        //WE <= 1'b0;
        R_mainstate <= 4'd0;
    end
    else begin
        case (R_mainstate)
            4'd0 : begin
                Data <= {"    VOLTMETER   "};
                WrAddress <= 4'd0;
                //WE <= 1'b1;
                R_mainstate <= 4'd1;
            end 
            4'd1 :begin
                Data <= {"voltage : ",new_bcd_t,"V"};
                WrAddress <= 4'd1;
                R_mainstate <= 4'd2;
            end
            4'd2 : begin
                Data <= {"liuyunfeng  edit"};
                WrAddress <= 4'd2;
                //WE <= 1'b1;
                R_mainstate <= 4'd3;
            end
            4'd3 :begin
                if(~key_scan) begin
                    Data <= {"     LOCKED     "};
                end
                else begin
                    Data <= {"     RUNNING    "};
                end
                
                WrAddress <= 4'd3;
                R_mainstate <= 4'd0;
            end
            default: begin
                WrAddress <= 4'd0;
                Data <= 128'd0;
                //WE <= 1'b0;
                R_mainstate <= 4'd0;
            end
        endcase
    end
end

 

四、测试:

      分为三组测试内容,分别为电压扫描测试、电压锁定测试和电压对比测试,具体测试详见b站视频。

五、遇到问题:

       在开发中遇到电压校准存在问题,且误差较大,需要后续进行再次校正;同时,由于OLED 采用他人开源代码,导致并不是能非常方便的上手修改与使用,后续会重新修改方便扩展使用。

六、项目总结:

       在本次项目开发中,首次接触到lattice的fpga和他的EDA工具,刚上手废了一段时间学习新的FPGA,并开发测试;同时在完成本项目中更加深刻的学习了SPI通信和OLED 的驱动方式。由于所用FPGA较少,本次项目也是系统的使用了一个全新的。轻量级FPGA平台完成了一次具备实用功能的项目,虽然本次项目完成还有很多不足,尤其是在电压测量精度上还有较大的改进。

附录:

   完整的项目代码也已上传至我的github仓库:https://github.com/kevinliuyunfeng/STEP_MXO2_ADC.git

 

附件下载
基于小脚丫FPGA综合训练平台的电压表.rar
包括bit流文件和完整的工程文件
团队介绍
西安电子科技大学 计算机科学与技术学院
团队成员
十年风雪
评论
0 / 100
查看更多
目录
硬禾服务号
关注最新动态
0512-67862536
info@eetree.cn
江苏省苏州市苏州工业园区新平街388号腾飞创新园A2幢815室
苏州硬禾信息科技有限公司
Copyright © 2024 苏州硬禾信息科技有限公司 All Rights Reserved 苏ICP备19040198号