一、项目要求
-
旋转电位计可以产生0-3.3V的电压
-
利用板上的串行ADC对电压进行转换
-
将电压值在板上的OLED屏幕上显示出来
二、平台介绍
小脚丫的fpga综合训练板具有丰富的外设,在原来小脚丫fpga开发板的外设之外,又扩展了USB转串口,OLED屏幕,ADC,温度传感器,10位R-2R网络DDS,蜂鸣器。本次设计主要使用了ADC和OLED模块。
2.1 ADC模块
训练板的AD使用ADS7868芯片,是一款8位ADC速度能达到200KSPS,通过SPI总线将输出传输出来。电路还包含了一个可调电位器产生0-3.3v的电压。
2.2 OLED模块
训练板上集成了一个OLED12832模块,使用SPI串行总线通信,屏幕集成了SSD1206驱动芯片进行屏幕驱动。
2.3 管脚分配
小脚丫FPGA模块的管脚 | DAC的数据位 |
x | ClkIn |
Pin 31 | DA[9] |
Pin 30 | DA[8] |
Pin 29 | DA[7] |
Pin 28 | DA[6] |
Pin 27 | DA[5] |
Pin 26 | DA[4] |
Pin 25 | DA[3] |
Pin 24 | DA[2] |
Pin 23 | DA[1] |
Pin 22 | DA[0] |
Pin 19 | PWM_Out |
Pin 39 | OledClk |
Pin 38 | OledDin |
Pin 37 | OledRes |
Pin 36 | OledDc |
Pin 35 | OledCs |
Pin 2 | UartRx |
Pin 3 | UartTx |
Pin 12 | AdcSdo |
Pin 13 | AdcClk |
Pin 14 | AdcCs |
Pin 17 | DS18B20 |
Pin 18 | Beeper |
三、Verilog程序编写 3.1 顶层例化
顶层例化文件,连接了各个模块,将ADC和OLED都放在了同一个文件中,读取ADC数值,将ADC获取的原始二进制数据乘以129将原始的0-255数据与0-3.3V进行对应,然后使用二进制转BCD码模块,转换得到BCD码,输出到OLED模块,在OLED中进行输出。
module Volt_Oled(
input clk,
input rst_n,
output adc_cs,
output adc_clk,
input adc_dat,
output oled_csn,
output oled_rst,
output oled_dcn,
output oled_clk,
output oled_dat
);
wire adc_once;
wire [7:0] adc_data;
ADS7868 ADS7868(
.clk (clk),
.rst_n (rst_n),
.adc_cs (adc_cs),
.adc_clk (adc_clk),
.adc_dat (adc_dat),
.adc_done (adc_once),
.adc_data (adc_data)
);
wire [15:0] bin_code = adc_data * 16'd129;
wire [19:0] bcd_code;
bin_to_bcd bin_to_bcd(
.rst_n (rst_n),
.bin_code (bin_code),
.bcd_code (bcd_code)
);
OLED12832 OLED12832(
.clk (clk),
.rst_n (rst_n),
.datain (bcd_code),
.oled_csn (oled_csn),
.oled_rst (oled_rst),
.oled_dcn (oled_dcn),
.oled_clk (oled_clk),
.oled_dat (oled_dat)
);
endmodule
3.2 ADS7868驱动
ADS7868时序图:
使用了小脚丫官方提供的ADS7868驱动程序:
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
3.3 BIN转BCD模块
BCD码称为二进码十进数或BCD码(Binary-Coded Decimal)亦称二进码十进数或二-十进制代码。用4位二进制数来表示1位十进制数中的0~9这10个数码。
BCD码这种编码形式利用了四个位元来储存一个十进制的数码,使二进制和十进制之间的转换得以快捷的进行。这种编码技巧,最常用于会计系统的设计里,因为 会计制度经常需要对很长的数字串作准确的计算。相对于一般的浮点式记数法,采用BCD码,既可保存数值的精确度,又可免却使电脑作浮点运算时所耗费的时 间。此外,对于其他需要高精确度的计算,BCD编码亦很常用。
在本项目中,由于需要屏幕输出,所以需要将二进制数据转换成BCD码,再传输到屏幕模块进行输出。
常用的转换方式:
- 除法取余的方法
- Bin2bcd移位算法
由于除法取余算法对性能的消耗比较大,所以设计中使用了移位算法。
Bin2bcd移位算法原理:
此处要介绍的是二进制转BCD码的硬件实现,采用左移加3的算法,具体描述如下:
(此处以8-bit 二进制码为例)
- 左移要转换的二进制码1位
- 左移之后,BCD码分别置于百位、十位、个位
- 如果移位后所在的BCD码列大于或等于5,则对该值加3
- 继续左移的过程直至全部移位完成
module bin_to_bcd
(
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
3.4 OLED12832驱动
这里直接使用了小脚丫提供的驱动,修改了MAIN部分
MAIN:begin
if(cnt_main >= 5'd10) 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'hb1; x_ph <= 8'h10; x_pl <= 8'h00; num <= 5'd16; char <= " Voltage ";state <= SCAN; end
5'd2: begin y_p <= 8'hb0; 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'hb2; x_ph <= 8'h12; x_pl <= 8'h00; num <= 5'd 1; char <= datain[19:16]; state <= SCAN; end
5'd6: begin y_p <= 8'hb2; x_ph <= 8'h12; x_pl <= 8'h08; num <= 5'd 1; char <= "."; state <= SCAN; end
5'd7: begin y_p <= 8'hb2; x_ph <= 8'h13; x_pl <= 8'h00; num <= 5'd 1; char <= datain[15:12]; state <= SCAN; end
5'd8: begin y_p <= 8'hb2; x_ph <= 8'h13; x_pl <= 8'h08; num <= 5'd 1; char <= datain[11:8]; state <= SCAN; end
5'd9: begin y_p <= 8'hb2; x_ph <= 8'h14; x_pl <= 8'h00; num <= 5'd 1; char <= datain[7:4]; state <= SCAN; end
5'd10: begin y_p <= 8'hb2; x_ph <= 8'h14; x_pl <= 8'h08; num <= 5'd 1; char <= datain[3:0]; state <= SCAN; end
default: state <= IDLE;
endcase
end
四、使用资源
PAD Specification File
***************************
PART TYPE: LCMXO2-4000HC
Performance Grade: 4
PACKAGE: CSBGA132
Package Status: Final Version 1.44
Sat Sep 04 21:59:36 2021
Pinout by Port Name:
+-----------+----------+--------------+-------+-----------+-----------+--------------------------------------+
| Port Name | Pin/Bank | Buffer Type | Site | PG Enable | BC Enable | Properties |
+-----------+----------+--------------+-------+-----------+-----------+--------------------------------------+
| adc_clk | N5/2 | LVCMOS33_OUT | PB10A | | | DRIVE:8mA PULL:DOWN SLEW:SLOW |
| adc_cs | P6/2 | LVCMOS33_OUT | PB13B | | | DRIVE:8mA PULL:DOWN SLEW:SLOW |
| adc_dat | L3/3 | LVCMOS33_IN | PL19B | | | PULL:DOWN CLAMP:ON HYSTERESIS:SMALL |
| clk | C1/5 | LVCMOS33_IN | PL4A | | | PULL:DOWN CLAMP:ON HYSTERESIS:SMALL |
| oled_clk | E12/1 | LVCMOS33_OUT | PR5A | | | DRIVE:8mA PULL:DOWN SLEW:SLOW |
| oled_csn | F14/1 | LVCMOS33_OUT | PR8B | | | DRIVE:8mA PULL:DOWN SLEW:SLOW |
| oled_dat | F12/1 | LVCMOS33_OUT | PR6B | | | DRIVE:8mA PULL:DOWN SLEW:SLOW |
| oled_dcn | F13/1 | LVCMOS33_OUT | PR8A | | | DRIVE:8mA PULL:DOWN SLEW:SLOW |
| oled_rst | G12/1 | LVCMOS33_OUT | PR9A | | | DRIVE:8mA PULL:DOWN SLEW:SLOW |
| rst_n | L14/1 | LVCMOS33_IN | PR16A | | | PULL:DOWN CLAMP:ON HYSTERESIS:SMALL |
+-----------+----------+--------------+-------+-----------+-----------+--------------------------------------+
Vccio by Bank:
+------+-------+
| Bank | Vccio |
+------+-------+
| 1 | 3.3V |
| 2 | 3.3V |
| 3 | 3.3V |
| 5 | 3.3V |
+------+-------+
五、不足
由于时间和能力有限,没能实现所有的模块自行编写,使用了官方提供驱动。