项目背景:
微控制器作为目前嵌入式系统设计的主力军在各行各业得到了广泛的应用,但随着物联网、智能硬件、VR等一系列新兴概念产品的问世,市场产品有明显的多样化趋势,功能更为丰富,因而这也给工程师在嵌入式产品设计的时候提出了新的挑战。举个例子,虽然如今的微控制器产品系列细分化更为彻底,对每个层次的领域都有相关的MCU产品支持,但这也意味着器件的选型和资源评估需要更加谨慎,传统MCU开发平台的选型就是一个难题,对于讲究适用就够的原则,选性能功能强大丰富的微控制器浪费资源、浪费成本,选低端的入门级微控制器可能又会出现功能不支持,IO口不足等问题,另外不同平台的有不同的开发环境流程等要熟悉,这也大大延长了工程师在项目开发中的时间;而在另一个对于以前来说相对小众的FPGA领域中,随着工艺的进步和EDA设计工具的不断发展,FPGA的集成度越来越高,而对应的功耗和成本却在不断降低,FPGA的门槛(学习成本和价格成本)也相应地越来越低,因而也使得其被广泛应用到各种领域中去,越来越多的嵌入式系统设计直接采FPGA设计,或者使用FPGA产品作为系统功能的拓展,总之,目前的嵌入式系统设计中越来越多的出现FPGA的身影。
功能描述:
- 使用旋转电位计产生0-3.3V的电压
- 利用板上的串行ADC对电压进行转换
- 将电压值在板上的OLED屏幕上显示出来
模块划分:
模块框图:
程序的大体流程为:frequency_divider模块将晶振信号分频,送给ADC模块作为ADC模块的时钟。旋钮通过分压产生0~3.3V的电压,ADC模块读取电压转成8位二进制数,送入binary2bcd模块,接口为 input [W-1 :0] bin;转成BCD码,接口为output reg [W+(W-4)/3:0] bcd。最后将BCD码送入OLED模块进行显示
时钟分频:
module frequency_divider(clk,clk_1,clk_10us,RST);
input clk,RST;
output clk_1,clk_10us;
reg[22:0]counter=1'b0;
reg[5:0]counter2=1'b0;
reg clk_1=1'b1;
reg clk_10us=1'b1;
always@(posedge clk or negedge RST)
begin
if(RST==0) counter=9'b0;
else if(counter==23'd599999)
begin
counter<=9'b0;
clk_1<=~clk_1;
end
else
counter<=counter+1'b1;
end
always@(posedge clk )
begin
if(counter2>60)
begin
counter2<=0;
clk_10us=~clk_10us;
end
else counter2<=counter2+1;
end
endmodule
frequency_divider u_fre(.clk(CLK), //分频
.clk_1(CLK_10Hz), //10Hz信号作为ADC使能信号
.clk_10us(clk_10us), //100kHz信号作为ADC时钟
.RST(RST));
根据ADS7868时序图,SCLK为时钟,当CS脚出现下降沿后,4个时钟脉冲后SDO开始输出数据,编写ADC的驱动如下:
ADC驱动:
reg [7:0]DATA; //ADC数据
reg [4:0]times; //计数
assign ADC_CLK=clk_10us;
assign ADC_CS=CLK_10Hz;
assign LED[7:0]=~DATA[7:0];
always@(negedge clk_10us) //读取ADC的值
begin
if(CLK_10Hz==0)
begin
times=times+1;
if(times>=5&×<13)
begin
DATA[7:1]=DATA[6:0];
DATA[0]=ADC_DO;
end
else if(times>=13) times=15;
end
else times=0;
end
电压计算:
wire [8:0]ADC_buf;
assign ADC_buf=(DATA*330)/255; //计数电压
二进制转BCD码:
module binary2bcd
#( parameter W = 9) // input width
( input [W-1 :0] bin , // binary
output reg [W+(W-4)/3:0] bcd ); // bcd {...,thousands,hundreds,tens,ones}
integer i,j;
always @(bin) begin
for(i = 0; i <= W+(W-4)/3; i = i+1) bcd[i] = 0; // initialize with zeros
bcd[W-1:0] = bin; // initialize with input vector
for(i = 0; i <= W-4; i = i+1) // iterate on structure depth
for(j = 0; j <= i/3; j = j+1) // iterate on structure width
if (bcd[W-i+4*j -: 4] > 4) // if > 4
bcd[W-i+4*j -: 4] = bcd[W-i+4*j -: 4] + 4'd3; // add 3
end
endmodule
wire [11:0]bcd;
binary2bcd U_bcd( .bin(ADC_buf), //将电压数据转成BCD码
.bcd(bcd)
);
OLED显示:
OLED显示模块同样引用自电子森林的应用案例及参考代码,代码部分省略,展示修改部分
MAIN:begin
if(cnt_main >= 5'd8) cnt_main <= 7;//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 <= " ADC-Test ";state <= SCAN; end
5'd2: begin y_p <= 8'hb1; x_ph <= 8'h10; x_pl <= 8'h00; num <= 5'd16; char <= "Voltage: ";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
//前4行是初始化刷新
5'd5: begin y_p <= 8'hb1; x_ph <= 8'h15; x_pl <= 8'h00; num <= 5'd6 ; char <= " "; state <= SCAN; end //char如果超过num,只取后面的num个
5'd6: begin y_p <= 8'hb1; x_ph <= 8'h15; x_pl <= 8'h00; num <= 5'd6 ; char <= " ";state <= SCAN; end
5'd7: begin y_p <= 8'hb2; x_ph <= 8'h15; x_pl <= 8'h00; num <= 5'd5 ; char <= {4'd0,ADC_H,".",4'd0,ADC_M,4'b0,ADC_L,"V"};state <= SCAN; end //{4'd0,hour_high,4'd0,hour_low,":",4'd0,min_high,4'd0,min_low};state <= SCAN; end
5'd8: begin y_p <= 8'hb3; x_ph <= 8'h15; x_pl <= 8'h00; num <= 5'd2 ; char <= {4'd0,test_L,4'd0,test_H};state <= SCAN; end
//后4行是更新部分
default: state <= IDLE;
endcase
end
OLED12832 i_OLED( .clk(CLK), //12MHz系统时钟
.rst_n(RST), //系统复位,低有效
.test_H(DATA[7:4]), //测试用
.test_L(DATA[3:0]), //测试用
.ADC_H(bcd[11:8]), //ADC第一位
.ADC_M(bcd[7:4]), //ADC第二位
.ADC_L(bcd[3:0]), //ADC第三位
.oled_csn(oled_csn), //OLCD液晶屏使能
.oled_rst(oled_rst), //OLCD液晶屏复位
.oled_dcn(oled_dcn), //OLCD数据指令控制
.oled_clk(oled_clk), //OLCD时钟信号
.oled_dat(oled_dat) //OLCD数据信号
);
引脚分配:
引脚分配如图所示,一定要设置正确,否则无法获得数据。
资源占用:
Design Summary:
Number of registers: 233 out of 4635 (5%)
PFU registers: 233 out of 4320 (5%)
PIO registers: 0 out of 315 (0%)
Number of SLICEs: 734 out of 2160 (34%)
SLICEs as Logic/ROM: 734 out of 2160 (34%)
SLICEs as RAM: 0 out of 1620 (0%)
SLICEs as Carry: 134 out of 2160 (6%)
Number of LUT4s: 1467 out of 4320 (34%)
Number used as logic LUTs: 1199
Number used as distributed RAM: 0
Number used as ripple logic: 268
Number used as shift registers: 0
Number of PIO sites used: 18 + 4(JTAG) out of 105 (21%)
Number of block RAMs: 0 out of 10 (0%)
Number of GSRs: 1 out of 1 (100%)
EFB used : No
JTAG used : No
Readback used : No
Oscillator used : No
Startup used : No
POR : On
Bandgap : On
Number of Power Controller: 0 out of 1 (0%)
Number of Dynamic Bank Controller (BCINRD): 0 out of 6 (0%)
Number of Dynamic Bank Controller (BCLVDSO): 0 out of 1 (0%)
Number of DCCA: 0 out of 8 (0%)
Number of DCMA: 0 out of 2 (0%)
Number of PLLs: 0 out of 2 (0%)
Number of DQSDLLs: 0 out of 2 (0%)
Number of CLKDIVC: 0 out of 4 (0%)
Number of ECLKSYNCA: 0 out of 4 (0%)
Number of ECLKBRIDGECS: 0 out of 2 (0%)
项目总结:
本次实验历经两周,前一周查看各类资料,熟悉开发板并完成各模块代码,后一周开始正式着手与项目。再这次的实验中,我学会了查阅芯片手册,并根据时序图编写驱动。在这期间,我记录的一些笔记:
- input、output和inout默认类型是wire型,连续赋值assign语句针对线网变量赋值。线网变量一般对应到FPGA中的一段连线,连线的值会随着他的驱动源的变化而变化,故称连续赋值语句。对于实际的数字电路,线网类型实际上对应着硬件连线,起连接作用。wire是线网最常用的一种数据类型,常用来表示以assign关键字指定的组合逻辑信号。
- Verilog HDL中绝大多数运算操作符都是可综合的:+、-、!(逻辑非)、~、&、~&、|、~|、^、~^、*、/、%、<<、>>、<、<=、>、>=、==、!(逻辑不等于)、&&、||。
行为级描述方式:always@(A,B,C)—@是事件等待语句,always不断循环等待A、B和C是从高变低,还是从低变高,都会执行always下的begin……end中语句,若ABC都没有变化,always也将不往下执行,一直循环等待。 - 可写always@(*)组合逻辑电路的描述方式,()表示全部的敏感变量,只要有任何输入信号变化,其输出立即发送变化。
- 时序电路基础: 锁存器、触发器、寄存器、计数器
在一开始,驱动总是无法读取ADS7868数据,但是在群友的帮助下,我最终找到了问题,非常感谢大家的帮助!