数字电压表
摘要:基于ADC将旋转电位计得到的电压进行转换,然后在用左移加3的方法将二进制数转换成BCD码,最后将电压信息传送到OLED 屏幕上显示出来
1项目背景
项目要求 - 利用ADC制作一个数字电压表
- 旋转电位计可以产生0-3.3V的电压
- 利用板上的串行ADC对电压进行转换
- 将电压值在板上的OLED屏幕上显示出来
- 项目原理图
实物图:
2部分代码展示
ADC信号采集//采用电子森林模板
模数转换器即A/D转换器,或简称ADC,通常是指一个将模拟信号转变为数字信号的电子元件。通常的模数转换器是将一个输入电压信号转换为一个输出的数字信号。由于数字信号本身不具有实际意义,仅仅表示一个相对大小。故任何一个模数转换器都需要一个参考模拟量作为转换的标准,比较常见的参考标准为最大的可转换信号大小。而输出的数字量则表示输入信号相对于参考信号的大小。模数转换一般要经过采样、量化和编码这几个步骤:采样是指用每隔一定时间的信号样值序列来代替原来在时间上连续的信号,也就是在时间上将模拟信号离散化,量化是用有限个幅度值近似原来连续变化的幅度值,把模拟信号的连续幅度变为有限数量的有一定间隔的离散值而编码则是按照一定的规律,把量化后的值用二进制数字表示,然后转换成二值或多值的数字信号流。
module ADC081S101_driver
(
input clk, //系统时钟
input rst_n, //系统复位,低有效
input adc_dat, //SPI总线SDA
output reg adc_cs,//SPI总线CS
output reg adc_clk, //SPI总线SCK
output wire oled_csn, //OLCD液晶屏使能
output wire oled_rst, //OLCD液晶屏复位
output wire oled_dcn, //OLCD数据指令控制
output wire oled_clk, //OLCD时钟信号
output wire oled_dat //OLCD数据信号
);
reg adc_done; //ADC采样完成标志
reg [7:0] adc_data; //ADC采样数据
reg [7:0] ss ;
parameter HIGH =1;
parameter LOW = 0;
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;
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//采集电压
BCD转换
量化运算 N = 256 * Vin / Vref,那么逆向运算为Vin = N * Vref / 256,其中Vref = 3.3V,所以Vin = N * 0.0129所以我们需要用FPGA计算adc_data * 0.0129的结果,然后为了使用十进制的显示,先将结果进行BCD转码,然后显示在OLED 屏幕上。为了方便计算直接乘以129,得到的数据经过BCD转码后小数点左移4位即可,将二进制数转换成BCD码的形式,采用左移加三的算法: 1、左移要转换的二进制码1位 2、左移之后,BCD码分别置于百位、十位、个位 3、如果移位后所在的BCD码列大于或等于5,则对该值加3 4、继续左移的过程直至全部移位完成
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];
ss = shift_reg[35:28];//电压的前2位
end
end//BCD
OLED 显示部分
SSD1306驱动的128*32分辨率的OLED屏幕,从功能上可以划分成两部分,驱动芯片电路 和 OLED点阵硬件.我们驱动该OLED屏幕显示,实际是与驱动芯片SSD1306通信,让SSD1306控制OLED点阵显示。SSD1306相当于一个中介所以我们需要了解SSD1306的功能、寄存器、总线、驱动流程等参数或工作方式,根据SSD1306的工作方式通信即可。
采用电子森林模板直接将要显示的电压的信息与原模块中的sw相匹配完成屏幕显示
OLED12832 U1
(
.clk(clk),
.rst_n(rst_n),
.sw(ss), //将电压数值传输到sw中.
.oled_csn (oled_csn),
.oled_rst(oled_rst),
.oled_dcn(oled_dcn),
.oled_clk(oled_clk),
.oled_dat(oled_dat)
);
OLED 主函数
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 <= "OLED TEST . ";state <= SCAN; end
5'd2: begin y_p <= 8'hb1; x_ph <= 8'h10; x_pl <= 8'h00; num <= 5'd16; char <= " ";state <= SCAN; end
5'd3: begin y_p <= 8'hb2; x_ph <= 8'h10; x_pl <= 8'h00; num <= 5'd16; char <= " ";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'hb0; x_ph <= 8'h15; x_pl <= 8'h00; num <= 5'd 1; char <= sw[7:4]; state <= SCAN; end
5'd6: begin y_p <= 8'hb0; x_ph <= 8'h16; x_pl <= 8'h00; num <= 5'd 1; char <= sw[3:0]; state <= SCAN; end
default: state <= IDLE;
endcase
end
最后附上TOP模块代码:
module ADC081S101_driver
(
input clk, //系统时钟
input rst_n, //系统复位,低有效
input adc_dat, //SPI总线SDA
output reg adc_cs,//SPI总线CS
output reg adc_clk, //SPI总线SCK
output wire oled_csn, //OLCD液晶屏使能
output wire oled_rst, //OLCD液晶屏复位
output wire oled_dcn, //OLCD数据指令控制
output wire oled_clk, //OLCD时钟信号
output wire oled_dat //OLCD数据信号
);//直接以ADC081S101为主体,其中完成了ADC采集信号并完成BCD转化,并在其中调用例化的OLED模块即可
//OLED例化模块
OLED12832 U1
(
.clk(clk),
.rst_n(rst_n),
.sw(ss),//将BCD转化后数据传入sw中
.oled_csn (oled_csn),
.oled_rst(oled_rst),
.oled_dcn(oled_dcn),
.oled_clk(oled_clk),
.oled_dat(oled_dat)
);
3总结和建议
Verilog语言之前从没学过,只是对C语言学了一会但也学的不是很好,自学这个Verilog感觉和C语言有很多相似的地方,但是在学习过程中还是遇到了很多问题就如在例化模块时开始用的reg定义变量但是会报错但是改成wire定义就可以了.虽然勉强完成了这个项目要求但是我感觉自己对Verilog语言还有很多地方不是很理解.搞这个项目让我感觉到了自己还有很多东西都不知道并且自己学过的很多知识都没有灵活的运用比如数电里学的一些关于FPGA的知识,学过了但一学期后需要用到的时候感觉就是基本上都忘了,通过这次的项目活动让我发现了自己在学习上的方式方法有问题导致没有真正学到东西,并且还有很多的知识都不是很理解,所以我需要调整自己的学习状态争取以后能够做到学以致用.