一.项目需求:
利用ADC制作一个数字电压表
1.旋转电位计可以产生0-3.3V的电压
2.利用板上的串行ADC对电压进行转换
3.将电压值在板上的OLED屏幕上显示出来
二.思路及框架构建:
按照项目要求来实现,思路已经很明确了,根据开发板的原理图可以看出来,板上的旋转电位计与串行(SPI)ADC芯片ADS7868相连。我们可以通过ADS7868对旋转电位计的模拟量进行采集,然后实时的显示在两个数码管和OLED屏幕上,这样就可以基本完成项目的要求了。
确定思路以后,就需要分别设计模块,然后把各功能综合在一起,这样就能完成项目了。具体划分以后,我们大概需要五个部分的模块来实现最后的功能:首先是利用串行ADC进行模拟量读取,需要用到SPI总线和ADC的相关知识;然后是设计一个转码模块,将采集到的模拟量转为数字量之后的二进制数字变为十进制,方便数码管和OLED的显示;其次是数码管显示模块和OLED显示模块;最后是综合以上四个模块的一个top模块,这样就可以了。
模块划分:
- voltmeter
- ADS7868
- bintobcd
- segled
- OLEDshow
管脚映射:
input:
output:
逻辑图:
三.代码实现:
硬禾学堂和电子森林里提供了很多模块的实验例程,有一部分是封装好的,不需要怎么改动,还有一些是我们可以根据项目需求进行调整的。总之,我们可以在网站上找到很多有用的例程进行修改来完成我们的项目。这里我将把每一部分的代码进行主要的说明。
顶层模块 voltmeter 本部分综合了所有剩余的模块,控制整个项目的框架,其中ADC采样数据乘以0.0130时可以完整的显示电压表示数,从0.0-3.3V。
module voltmeter
(
input clk, //系统时钟
input rst_n, //系统复位,低有效
output adc_cs, //SPI总线CS
output adc_clk, //SPI总线SCK
input adc_dat, //SPI总线SDA
output seg1_sel, //数码管位选
output [7:0] seg1_led, //数码管段选
output seg2_sel, //数码管位选
output [7:0] seg2_led, //数码管段选
output oled_csn, //OLCD液晶屏使能
output oled_rst, //OLCD液晶屏复位
output oled_dcn, //OLCD数据指令控制
output oled_clk, //OLCD时钟信号
output oled_dat //OLCD数据信号
);
wire adc_done;
wire [7:0] adc_data;
//ADC功能,例化
ADS7868 u2
(
.clk (clk ), //系统时钟
.rst_n (rst_n ), //系统复位,低有效
.adc_cs (adc_cs ), //SPI总线CS
.adc_clk (adc_clk ), //SPI总线SCK
.adc_dat (adc_dat ), //SPI总线SDA
.adc_done (adc_done ), //ADC采样完成标志
.adc_data (adc_data ) //ADC采样数据
);
//将ADC采样数据按规则转换为电压数据(乘以0.0130),这里我们直接乘以129,得到的数据经过BCD转码后小数点左移4位即可
wire [15:0] bin_code = adc_data * 16'd130;
wire [19:0] bcd_code;
//将处理后的ADC数据进行BCD转码,例化
bintobcd u3
(
.rst_n (rst_n ), //系统复位,低有效
.bin_code (bin_code ), //需要进行BCD转码的二进制数据
.bcd_code (bcd_code ) //转码后的BCD码型数据输出
);
//Segment led display module
segled seg[1:0]
(
.seg_data (bcd_code[19:12] ), //seg_data input
.seg_dot ({1'b1,1'b0} ), //segment dot control
.seg_sel ({seg1_sel,seg2_sel}), //segment com port
.seg_led ({seg1_led,seg2_led}) //MSB~LSB = DP,G,F,E,D,C,B,A
);
OLEDshow u4
(
.clk (clk ), //12MHz系统时钟
.rst_n (rst_n ), //系统复位,低有效
.data (bcd_code[19:12]),
.oled_clk (oled_clk ),
.oled_csn (oled_csn ),
.oled_dat (oled_dat ),
.oled_dcn (oled_dcn ),
.oled_rst (oled_rst )
);
endmodule
ADC采样模块 ADS7868 本模块完全采用了电子森林里ADC采样的实例模块,也是方便了我们程序的编写,直接拿来用就可以了。这里也希望大家能多多挖掘各网站上对自己完成项目有用的知识,很多已经完成了的固定用法的模块我们就可以直接享用前人的成果了,在此也感谢创作者。
module ADS7868
(
input clk, //系统时钟
input rst_n, //系统复位,低有效
output reg adc_cs, //SPI总线CS
output reg adc_clk, //SPI总线SCK
input adc_dat, //SPI总线SDA
output reg adc_done, //ADC采样完成标志
output reg [7:0] adc_data //ADC采样数据
);
localparam HIGH = 1'b1;
localparam LOW = 1'b0;
reg [7:0] cnt; //计数器
always @(posedge clk or negedge rst_n)
if(!rst_n) cnt <= 1'b0;
else if(cnt >= 8'd34) cnt <= 1'b0;
else cnt <= cnt + 1'b1;
reg [7:0] data;
always @(posedge clk or negedge rst_n)
if(!rst_n) begin
adc_cs <= HIGH; adc_clk <= HIGH;
data <= 1'b0; adc_data <= 1'b0; adc_done <= LOW;
end else case(cnt)
8'd0 : begin adc_cs <= HIGH; adc_clk <= HIGH; end
8'd1 : begin adc_cs <= LOW; adc_clk <= HIGH; end
8'd2,8'd4,8'd6,8'd8,8'd10,8'd12,8'd14,8'd16,
8'd18,8'd20,8'd22,8'd24,8'd26,8'd28,8'd30,8'd32:
begin adc_cs <= LOW; adc_clk <= LOW; end
8'd3 : begin adc_cs <= LOW; adc_clk <= HIGH; end //0
8'd5 : begin adc_cs <= LOW; adc_clk <= HIGH; end //1
8'd7 : begin adc_cs <= LOW; adc_clk <= HIGH; end //2
8'd9 : begin adc_cs <= LOW; adc_clk <= HIGH; data[7] <= adc_dat; end //3
8'd11 : begin adc_cs <= LOW; adc_clk <= HIGH; data[6] <= adc_dat; end //4
8'd13 : begin adc_cs <= LOW; adc_clk <= HIGH; data[5] <= adc_dat; end //5
8'd15 : begin adc_cs <= LOW; adc_clk <= HIGH; data[4] <= adc_dat; end //6
8'd17 : begin adc_cs <= LOW; adc_clk <= HIGH; data[3] <= adc_dat; end //7
8'd19 : begin adc_cs <= LOW; adc_clk <= HIGH; data[2] <= adc_dat; end //8
8'd21 : begin adc_cs <= LOW; adc_clk <= HIGH; data[1] <= adc_dat; end //9
8'd23 : begin adc_cs <= LOW; adc_clk <= HIGH; data[0] <= adc_dat; end //10
8'd25 : begin adc_cs <= LOW; adc_clk <= HIGH; adc_data <= data; end //11
8'd27 : begin adc_cs <= LOW; adc_clk <= HIGH; adc_done <= HIGH; end //12
8'd29 : begin adc_cs <= LOW; adc_clk <= HIGH; adc_done <= LOW; end //13
8'd31 : begin adc_cs <= LOW; adc_clk <= HIGH; end //14
8'd33 : begin adc_cs <= LOW; adc_clk <= HIGH; end //15
8'd34 : begin adc_cs <= HIGH; adc_clk <= HIGH; end
default : begin adc_cs <= HIGH; adc_clk <= HIGH; end
endcase
endmodule
转码模块 bintobcd 本模块负责将ADC提供的二进制代码转为十进制,方便后续模块的显示。
module bintobcd
(
input rst_n, //系统复位,低有效
input [15:0] bin_code, //需要进行BCD转码的二进制数据
output reg [19:0] bcd_code //转码后的BCD码型数据输出
);
/*
此模块为了将ADC采样的数据转换为我们常用的十进制显示而存在,
主要知识涉及数学中不同制式数据的转换,详细原理这里不做介绍,去百度搜索<FPGA 二进制转BCD码>可得
*/
reg [35:0] shift_reg;
always@(bin_code or rst_n)begin
shift_reg = {20'h0,bin_code};
if(!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
endmodule
数码管显示 segled 用于在两个数码管上显示采集到的ADC数据,这也是可以在电子森林上找到的,不过多解释。
module segled
(
input [3:0] seg_data, //seg_data input
input seg_dot, //segment dot control
output seg_sel, //segment com port
output reg [7:0] seg_led //MSB~LSB = DP,G,F,E,D,C,B,A
);
always@(seg_data)
case(seg_data)
4'h0: seg_led = {seg_dot,7'h3f}; // 0
4'h1: seg_led = {seg_dot,7'h06}; // 1
4'h2: seg_led = {seg_dot,7'h5b}; // 2
4'h3: seg_led = {seg_dot,7'h4f}; // 3
4'h4: seg_led = {seg_dot,7'h66}; // 4
4'h5: seg_led = {seg_dot,7'h6d}; // 5
4'h6: seg_led = {seg_dot,7'h7d}; // 6
4'h7: seg_led = {seg_dot,7'h07}; // 7
4'h8: seg_led = {seg_dot,7'h7f}; // 8
4'h9: seg_led = {seg_dot,7'h6f}; // 9
4'ha: seg_led = {seg_dot,7'h77}; // A
4'hb: seg_led = {seg_dot,7'h7C}; // b
4'hc: seg_led = {seg_dot,7'h39}; // C
4'hd: seg_led = {seg_dot,7'h5e}; // d
4'he: seg_led = {seg_dot,7'h79}; // E
4'hf: seg_led = {seg_dot,7'h71}; // F
default: seg_led = {seg_dot,7'h00};
endcase
assign seg_sel = 1'b0; //共阴极,使能
endmodule
OLED显示 OLEDshow 负责把采集到的ADC数据显示在OLED屏上。此款开发板的OLED显示屏也是SPI形式通信的,我们可以找到使用它的基础程序,然后按照项目需求,把采集到的十进制电压值显示上去就可以了。此处只提供核心部分修改代码。
MAIN:begin
if(cnt_main >= 5'd6) cnt_main <= 5'd5;
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'd16; char <= "StepFPGATraining";state <= SCAN; end
5'd2: begin y_p <= 8'hb1; x_ph <= 8'h10; x_pl <= 8'h00; num <= 5'd16; char <= "Volt meter : ";state <= SCAN; end
5'd3: begin y_p <= 8'hb2; x_ph <= 8'h10; x_pl <= 8'h00; num <= 5'd16; char <= " . V ";state <= SCAN; end
5'd4: begin y_p <= 8'hb3; x_ph <= 8'h10; x_pl <= 8'h00; num <= 5'd16; char <= "----------------";state <= SCAN; end
5'd5: begin y_p <= 8'hb2; x_ph <= 8'h13; x_pl <= 8'h00; num <= 5'd 1; char <=data[7:4]; state <= SCAN; end
5'd6: begin y_p <= 8'hb2; x_ph <= 8'h14; x_pl <= 8'h00; num <= 5'd 1; char <=data[3:0]; state <= SCAN; end
default: state <= IDLE;
endcase
end
四.项目总结:
完成本次项目很简单,其主要原因是很多有用的、难的代码其实硬禾学堂和电子森林里都已经提供了现成的范例,我们只需要进行封装和适量的修改就可以了。比如说ADC的采集与OLED的通信等,已经为我们提供了很好的例程,很多是可以直接拿来用的,对于一个FPGA和Verilog的初学者来说这是很友好的。通过本次项目的学习,我大致了解了Verilog和FPGA的部分知识,为后面学校开设的课程进行了一个先导性的学习。同时,完成本次项目,还提高了我的信息搜集能力,能够在网上找到很多自己需要的东西,这是很有用的。剩余时间我又完成了项目二,FPGA制作音乐播放器,也从中学到了很多关于这方面的知识。
通过本次项目,我主要了解和掌握了:
- ADC及其工作模式;
- SPI通信模式;
- OLED的显示模式,看懂FPGA写OLED;
- 状态机和序列机;
设计制作中遇到的困难:
- 对ADC的不熟悉;
- 不清楚SPI的通信协议;
- 很难用OLED显示自己想显示的东西;