引言
终于在最后一天,抽出空来完成暑期一起练项目的最后一部分。在介绍的开头,我想吹一波电子森林,教程和介绍都很细,也很专业,从我的个人经历来看非常适合初学者。
之前也不是没有学过VHDL语言,本科时候上过课和实验,但那时候就是按照步骤机械的完成了内容,完全应试对付过了课程,对各个部分也没有深刻的体会,经过这次项目可以说是真正的认识到了FPGA到底是什么东西(之前一直以为就是个特殊点的单片机 =_= )
程序总体逻辑
好的话不多说,开门见山,首先我这个项目是数字电压计,那么必然少不了的就是ADC,按照之前的项目经验,我首先看了ADC芯片的数据手册,然后自信满满的打开了Diamond,发现和之前用的编译器完全不一样。没办法重头开始学,此处略去学习Diamond操作流程。
对于初学VHDL语言(verilog)和忘了的同学直接在电子森林中就可以回顾。我这次用的FPGA版本是小脚丫U盘版本(step-mxo2-c)操作极其简单,直接把jed文件拖进去就可以把程序烧进去。说实话一开始被这个操作震撼到了,之前一直是用仿真器来烧程序。
下面就逐步介绍每一部分的程序,虽然都是抄来的,但是我根据自己的使用习惯增加了部分注释,方便自己看懂。
adc部分
adc程序如下,我是在电子森林的示例中复制出来的,有需要的同学可以直接拿来用。唯一需要注意的就是二进制转换BCD码的时候如果精度不够,需要增大储存BCD码的参数的数据位,乘上一个更大的系数,然后修改BCD码转换的部分程序。
module adc
(
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 [15:0] bcd_code //ADC显示数据
);
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] adc_data; //ADC采样数据
reg [7:0] data; //预分配接收数据缓存地址
//取ADC中的数据
always @(posedge clk or negedge rst_n)
if(!rst_n) begin
adc_cs <= 1'b1; adc_clk <= 1'b1;
end else case(cnt)
8'd0 : begin adc_cs <= 1'b1; adc_clk <= 1'b1; end
8'd1 : begin adc_cs <= 1'b0; adc_clk <= 1'b1; 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 <= 1'b0; adc_clk <= 1'b0; end
8'd3 : begin adc_cs <= 1'b0; adc_clk <= 1'b1; end //0
8'd5 : begin adc_cs <= 1'b0; adc_clk <= 1'b1; end //1
8'd7 : begin adc_cs <= 1'b0; adc_clk <= 1'b1; end //2
8'd9 : begin adc_cs <= 1'b0; adc_clk <= 1'b1; data[7] <= adc_dat; end //3
8'd11 : begin adc_cs <= 1'b0; adc_clk <= 1'b1; data[6] <= adc_dat; end //4
8'd13 : begin adc_cs <= 1'b0; adc_clk <= 1'b1; data[5] <= adc_dat; end //5
8'd15 : begin adc_cs <= 1'b0; adc_clk <= 1'b1; data[4] <= adc_dat; end //6
8'd17 : begin adc_cs <= 1'b0; adc_clk <= 1'b1; data[3] <= adc_dat; end //7
8'd19 : begin adc_cs <= 1'b0; adc_clk <= 1'b1; data[2] <= adc_dat; end //8
8'd21 : begin adc_cs <= 1'b0; adc_clk <= 1'b1; data[1] <= adc_dat; end //9
8'd23 : begin adc_cs <= 1'b0; adc_clk <= 1'b1; data[0] <= adc_dat; end //10
8'd25 : begin adc_cs <= 1'b0; adc_clk <= 1'b1; adc_data <= data; end //11
8'd27 : begin adc_cs <= 1'b0; adc_clk <= 1'b1; adc_done <= 1'b1; end //12
8'd29 : begin adc_cs <= 1'b0; adc_clk <= 1'b1; adc_done <= 1'b0; end //13
8'd31 : begin adc_cs <= 1'b0; adc_clk <= 1'b1; end //14
8'd33 : begin adc_cs <= 1'b0; adc_clk <= 1'b1; end //15
8'd34 : begin adc_cs <= 1'b1; adc_clk <= 1'b1; end
default : begin adc_cs <= 1'b1; adc_clk <= 1'b1; end
endcase
wire [19:0] bin_code = adc_data * 20'd1294; // wire定义转换后的检测电压
reg [43:0] shift_reg; // reg定义BCD码转换
// BCD转换 将二进制转换为BCD码 并wire连接到oled模块显示出来
always@(bin_code or rst_n)begin
shift_reg = {24'h0,bin_code};
if(!rst_n) bcd_code = 0;
else begin
repeat(20) begin //循环16次
//BCD码各位数据作满5加3操作,
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;
if (shift_reg[39:36] >= 5) shift_reg[39:36] = shift_reg[39:36] + 2'b11;
if (shift_reg[43:40] >= 5) shift_reg[43:40] = shift_reg[43:40] + 2'b11;
shift_reg = shift_reg << 1;
end
bcd_code = shift_reg[43:28] ;
end
end
endmodule
oled部分
oled程序如下,这里也没什么好说的,就是偷懒,皮,直接用的电子森林示例中的oled代码,修改了显示部分的内容。但是有两点这里需要同学们注意:
- 我在循环程序的标志位使用的是adc完成标志 adc_done ,这就意味着每次adc识别完了就要刷新一次oled,这在产品中可能是不需要的,并且每一次数据都显示可能会把识别错的数显示出来,这里最好的做法是多次采样,然后数据滤波,最后刷新显示频率到30Hz差不多人眼看不出来频闪的程度就OK
- 我直接用的是ASCII码的全部字母和数字,其实可以自己用软件编一下字库,可以让程序精简很多,可以减少FPGA的占用,进一步提升效率。
module oled
(
input clk, //12MHz系统时钟
input rst_n, //系统复位,低有效
input wire [15:0] bcd_code, // bcd码
input wire adc_done, // ADC检测完成标志位
output reg oled_csn, //OLCD液晶屏使能
output reg oled_rst, //OLCD液晶屏复位
output reg oled_dcn, //OLCD数据指令控制
output reg oled_clk, //OLCD时钟信号
output reg oled_dat //OLCD数据信号
);
localparam INIT_DEPTH = 16'd25; //LCD初始化的命令的数量
localparam IDLE = 6'h1, MAIN = 6'h2, INIT = 6'h4, SCAN = 6'h8, WRITE = 6'h10, DELAY = 6'h20;
localparam HIGH = 1'b1, LOW = 1'b0;
localparam DATA = 1'b1, CMD = 1'b0;
reg [7:0] cmd [24:0];
reg [39:0] mem [122:0];
reg [7:0] y_p, x_ph, x_pl;
reg [(8*21-1):0] char;
reg [7:0] num, char_reg; //
reg [4:0] cnt_main, cnt_init, cnt_scan, cnt_write;
reg [15:0] num_delay, cnt_delay, cnt;
reg [5:0] state, state_back;
always@(posedge adc_done or negedge rst_n) begin
if(!rst_n) begin
cnt_main <= 1'b0; cnt_init <= 1'b0; cnt_scan <= 1'b0; cnt_write <= 1'b0;
y_p <= 1'b0; x_ph <= 1'b0; x_pl <= 1'b0;
num <= 1'b0; char <= 1'b0; char_reg <= 1'b0;
num_delay <= 16'd5; cnt_delay <= 1'b0; cnt <= 1'b0;
oled_csn <= HIGH; oled_rst <= HIGH; oled_dcn <= CMD; oled_clk <= HIGH; oled_dat <= LOW;
state <= IDLE; state_back <= IDLE;
end else begin
case(state)
IDLE:begin
cnt_main <= 1'b0; cnt_init <= 1'b0; cnt_scan <= 1'b0; cnt_write <= 1'b0;
y_p <= 1'b0; x_ph <= 1'b0; x_pl <= 1'b0;
num <= 1'b0; char <= 1'b0; char_reg <= 1'b0;
num_delay <= 16'd5; cnt_delay <= 1'b0; cnt <= 1'b0;
oled_csn <= HIGH; oled_rst <= HIGH; oled_dcn <= CMD; oled_clk <= HIGH; oled_dat <= LOW;
state <= MAIN; state_back <= MAIN;
end
MAIN:begin
if(cnt_main >= 5'd10) cnt_main <= 5'd7; //修改总循环数
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 <= "STEP YYDS! ";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 <= "2021/8/13 HXY";state <= SCAN; end
5'd5: begin y_p <= 8'hb2; x_ph <= 8'h10; x_pl <= 8'h08; num <= 5'd 1; char <= "."; state <= SCAN; end
5'd6: begin y_p <= 8'hb2; x_ph <= 8'h14; x_pl <= 8'h00; num <= 5'd 1; char <= "V"; state <= SCAN; end
//修改显示数字
5'd7: begin y_p <= 8'hb2; x_ph <= 8'h10; x_pl <= 8'h00; num <= 5'd 1; char <= bcd_code[15:12]; state <= SCAN; end
5'd8: begin y_p <= 8'hb2; x_ph <= 8'h11; x_pl <= 8'h00; num <= 5'd 1; char <= bcd_code[11:8]; state <= SCAN; end
5'd9: begin y_p <= 8'hb2; x_ph <= 8'h12; x_pl <= 8'h00; num <= 5'd 1; char <= bcd_code[7:4]; state <= SCAN; end
5'd10: begin y_p <= 8'hb2; x_ph <= 8'h13; x_pl <= 8'h00; num <= 5'd 1; char <= bcd_code[3:0]; state <= SCAN; end
default: state <= IDLE;
endcase
end
INIT:begin //初始化状态
case(cnt_init)
5'd0: begin oled_rst <= LOW; cnt_init <= cnt_init + 1'b1; end //复位有效
5'd1: begin num_delay <= 16'd25000; state <= DELAY; state_back <= INIT; cnt_init <= cnt_init + 1'b1; end //延时大于3us
5'd2: begin oled_rst <= HIGH; cnt_init <= cnt_init + 1'b1; end //复位恢复
5'd3: begin num_delay <= 16'd25000; state <= DELAY; state_back <= INIT; cnt_init <= cnt_init + 1'b1; end //延时大于220us
5'd4: begin
if(cnt>=INIT_DEPTH) begin //当25条指令及数据发出后,配置完成
cnt <= 1'b0;
cnt_init <= cnt_init + 1'b1;
end else begin
cnt <= cnt + 1'b1; num_delay <= 16'd5;
oled_dcn <= CMD; char_reg <= cmd[cnt]; state <= WRITE; state_back <= INIT;
end
end
5'd5: begin cnt_init <= 1'b0; state <= MAIN; end //初始化完成,返回MAIN状态
default: state <= IDLE;
endcase
end
SCAN:begin //刷屏状态,从RAM中读取数据刷屏
if(cnt_scan == 5'd11) begin
if(num) cnt_scan <= 5'd3;
else cnt_scan <= cnt_scan + 1'b1;
end else if(cnt_scan == 5'd12) cnt_scan <= 1'b0;
else cnt_scan <= cnt_scan + 1'b1;
case(cnt_scan)
5'd 0: begin oled_dcn <= CMD; char_reg <= y_p; state <= WRITE; state_back <= SCAN; end //定位列页地址
5'd 1: begin oled_dcn <= CMD; char_reg <= x_pl; state <= WRITE; state_back <= SCAN; end //定位行地址低位
5'd 2: begin oled_dcn <= CMD; char_reg <= x_ph; state <= WRITE; state_back <= SCAN; end //定位行地址高位
5'd 3: begin num <= num - 1'b1;end
5'd 4: begin oled_dcn <= DATA; char_reg <= 8'h00; state <= WRITE; state_back <= SCAN; end //将5*8点阵编程8*8
5'd 5: begin oled_dcn <= DATA; char_reg <= 8'h00; state <= WRITE; state_back <= SCAN; end //将5*8点阵编程8*8
5'd 6: begin oled_dcn <= DATA; char_reg <= 8'h00; state <= WRITE; state_back <= SCAN; end //将5*8点阵编程8*8
5'd 7: begin oled_dcn <= DATA; char_reg <= mem[char[(num*8)+:8]][39:32]; state <= WRITE; state_back <= SCAN; end
5'd 8: begin oled_dcn <= DATA; char_reg <= mem[char[(num*8)+:8]][31:24]; state <= WRITE; state_back <= SCAN; end
5'd 9: begin oled_dcn <= DATA; char_reg <= mem[char[(num*8)+:8]][23:16]; state <= WRITE; state_back <= SCAN; end
5'd10: begin oled_dcn <= DATA; char_reg <= mem[char[(num*8)+:8]][15: 8]; state <= WRITE; state_back <= SCAN; end
5'd11: begin oled_dcn <= DATA; char_reg <= mem[char[(num*8)+:8]][ 7: 0]; state <= WRITE; state_back <= SCAN; end
5'd12: begin state <= MAIN; end
default: state <= IDLE;
endcase
end
WRITE:begin //WRITE状态,将数据按照SPI时序发送给屏幕
if(cnt_write >= 5'd17) cnt_write <= 1'b0;
else cnt_write <= cnt_write + 1'b1;
case(cnt_write)
5'd 0: begin oled_csn <= LOW; end //9位数据最高位为命令数据控制位
5'd 1: begin oled_clk <= LOW; oled_dat <= char_reg[7]; end //先发高位数据
5'd 2: begin oled_clk <= HIGH; end
5'd 3: begin oled_clk <= LOW; oled_dat <= char_reg[6]; end
5'd 4: begin oled_clk <= HIGH; end
5'd 5: begin oled_clk <= LOW; oled_dat <= char_reg[5]; end
5'd 6: begin oled_clk <= HIGH; end
5'd 7: begin oled_clk <= LOW; oled_dat <= char_reg[4]; end
5'd 8: begin oled_clk <= HIGH; end
5'd 9: begin oled_clk <= LOW; oled_dat <= char_reg[3]; end
5'd10: begin oled_clk <= HIGH; end
5'd11: begin oled_clk <= LOW; oled_dat <= char_reg[2]; end
5'd12: begin oled_clk <= HIGH; end
5'd13: begin oled_clk <= LOW; oled_dat <= char_reg[1]; end
5'd14: begin oled_clk <= HIGH; end
5'd15: begin oled_clk <= LOW; oled_dat <= char_reg[0]; end //后发低位数据
5'd16: begin oled_clk <= HIGH; end
5'd17: begin oled_csn <= HIGH; state <= DELAY; end //
default: state <= IDLE;
endcase
end
DELAY:begin //延时状态
if(cnt_delay >= num_delay) begin
cnt_delay <= 16'd0; state <= state_back;
end else cnt_delay <= cnt_delay + 1'b1;
end
default:state <= IDLE;
endcase
end
end
//OLED配置指令数据
always@(posedge rst_n)
begin
cmd[ 0] = {8'hae};
cmd[ 1] = {8'h00};
cmd[ 2] = {8'h10};
cmd[ 3] = {8'h00};
cmd[ 4] = {8'hb0};
cmd[ 5] = {8'h81};
cmd[ 6] = {8'hff};
cmd[ 7] = {8'ha1};
cmd[ 8] = {8'ha6};
cmd[ 9] = {8'ha8};
cmd[10] = {8'h1f};
cmd[11] = {8'hc8};
cmd[12] = {8'hd3};
cmd[13] = {8'h00};
cmd[14] = {8'hd5};
cmd[15] = {8'h80};
cmd[16] = {8'hd9};
cmd[17] = {8'h1f};
cmd[18] = {8'hda};
cmd[19] = {8'h00};
cmd[20] = {8'hdb};
cmd[21] = {8'h40};
cmd[22] = {8'h8d};
cmd[23] = {8'h14};
cmd[24] = {8'haf};
end
//5*8点阵字库数据
always@(posedge rst_n)
begin
mem[ 0] = {8'h3E, 8'h51, 8'h49, 8'h45, 8'h3E}; // 48 0
mem[ 1] = {8'h00, 8'h42, 8'h7F, 8'h40, 8'h00}; // 49 1
mem[ 2] = {8'h42, 8'h61, 8'h51, 8'h49, 8'h46}; // 50 2
mem[ 3] = {8'h21, 8'h41, 8'h45, 8'h4B, 8'h31}; // 51 3
mem[ 4] = {8'h18, 8'h14, 8'h12, 8'h7F, 8'h10}; // 52 4
mem[ 5] = {8'h27, 8'h45, 8'h45, 8'h45, 8'h39}; // 53 5
mem[ 6] = {8'h3C, 8'h4A, 8'h49, 8'h49, 8'h30}; // 54 6
mem[ 7] = {8'h01, 8'h71, 8'h09, 8'h05, 8'h03}; // 55 7
mem[ 8] = {8'h36, 8'h49, 8'h49, 8'h49, 8'h36}; // 56 8
mem[ 9] = {8'h06, 8'h49, 8'h49, 8'h29, 8'h1E}; // 57 9
mem[ 10] = {8'h7C, 8'h12, 8'h11, 8'h12, 8'h7C}; // 65 A
mem[ 11] = {8'h7F, 8'h49, 8'h49, 8'h49, 8'h36}; // 66 B
mem[ 12] = {8'h3E, 8'h41, 8'h41, 8'h41, 8'h22}; // 67 C
mem[ 13] = {8'h7F, 8'h41, 8'h41, 8'h22, 8'h1C}; // 68 D
mem[ 14] = {8'h7F, 8'h49, 8'h49, 8'h49, 8'h41}; // 69 E
mem[ 15] = {8'h7F, 8'h09, 8'h09, 8'h09, 8'h01}; // 70 F
mem[ 32] = {8'h00, 8'h00, 8'h00, 8'h00, 8'h00}; // 32 sp
mem[ 33] = {8'h00, 8'h00, 8'h2f, 8'h00, 8'h00}; // 33 !
mem[ 34] = {8'h00, 8'h07, 8'h00, 8'h07, 8'h00}; // 34
mem[ 35] = {8'h14, 8'h7f, 8'h14, 8'h7f, 8'h14}; // 35 #
mem[ 36] = {8'h24, 8'h2a, 8'h7f, 8'h2a, 8'h12}; // 36 $
mem[ 37] = {8'h62, 8'h64, 8'h08, 8'h13, 8'h23}; // 37 %
mem[ 38] = {8'h36, 8'h49, 8'h55, 8'h22, 8'h50}; // 38 &
mem[ 39] = {8'h00, 8'h05, 8'h03, 8'h00, 8'h00}; // 39 '
mem[ 40] = {8'h00, 8'h1c, 8'h22, 8'h41, 8'h00}; // 40 (
mem[ 41] = {8'h00, 8'h41, 8'h22, 8'h1c, 8'h00}; // 41 )
mem[ 42] = {8'h14, 8'h08, 8'h3E, 8'h08, 8'h14}; // 42 *
mem[ 43] = {8'h08, 8'h08, 8'h3E, 8'h08, 8'h08}; // 43 +
mem[ 44] = {8'h00, 8'h00, 8'hA0, 8'h60, 8'h00}; // 44 ,
mem[ 45] = {8'h08, 8'h08, 8'h08, 8'h08, 8'h08}; // 45 -
mem[ 46] = {8'h00, 8'h60, 8'h60, 8'h00, 8'h00}; // 46 .
mem[ 47] = {8'h20, 8'h10, 8'h08, 8'h04, 8'h02}; // 47 /
mem[ 48] = {8'h3E, 8'h51, 8'h49, 8'h45, 8'h3E}; // 48 0
mem[ 49] = {8'h00, 8'h42, 8'h7F, 8'h40, 8'h00}; // 49 1
mem[ 50] = {8'h42, 8'h61, 8'h51, 8'h49, 8'h46}; // 50 2
mem[ 51] = {8'h21, 8'h41, 8'h45, 8'h4B, 8'h31}; // 51 3
mem[ 52] = {8'h18, 8'h14, 8'h12, 8'h7F, 8'h10}; // 52 4
mem[ 53] = {8'h27, 8'h45, 8'h45, 8'h45, 8'h39}; // 53 5
mem[ 54] = {8'h3C, 8'h4A, 8'h49, 8'h49, 8'h30}; // 54 6
mem[ 55] = {8'h01, 8'h71, 8'h09, 8'h05, 8'h03}; // 55 7
mem[ 56] = {8'h36, 8'h49, 8'h49, 8'h49, 8'h36}; // 56 8
mem[ 57] = {8'h06, 8'h49, 8'h49, 8'h29, 8'h1E}; // 57 9
mem[ 58] = {8'h00, 8'h36, 8'h36, 8'h00, 8'h00}; // 58 :
mem[ 59] = {8'h00, 8'h56, 8'h36, 8'h00, 8'h00}; // 59 ;
mem[ 60] = {8'h08, 8'h14, 8'h22, 8'h41, 8'h00}; // 60 <
mem[ 61] = {8'h14, 8'h14, 8'h14, 8'h14, 8'h14}; // 61 =
mem[ 62] = {8'h00, 8'h41, 8'h22, 8'h14, 8'h08}; // 62 >
mem[ 63] = {8'h02, 8'h01, 8'h51, 8'h09, 8'h06}; // 63 ?
mem[ 64] = {8'h32, 8'h49, 8'h59, 8'h51, 8'h3E}; // 64 @
mem[ 65] = {8'h7C, 8'h12, 8'h11, 8'h12, 8'h7C}; // 65 A
mem[ 66] = {8'h7F, 8'h49, 8'h49, 8'h49, 8'h36}; // 66 B
mem[ 67] = {8'h3E, 8'h41, 8'h41, 8'h41, 8'h22}; // 67 C
mem[ 68] = {8'h7F, 8'h41, 8'h41, 8'h22, 8'h1C}; // 68 D
mem[ 69] = {8'h7F, 8'h49, 8'h49, 8'h49, 8'h41}; // 69 E
mem[ 70] = {8'h7F, 8'h09, 8'h09, 8'h09, 8'h01}; // 70 F
mem[ 71] = {8'h3E, 8'h41, 8'h49, 8'h49, 8'h7A}; // 71 G
mem[ 72] = {8'h7F, 8'h08, 8'h08, 8'h08, 8'h7F}; // 72 H
mem[ 73] = {8'h00, 8'h41, 8'h7F, 8'h41, 8'h00}; // 73 I
mem[ 74] = {8'h20, 8'h40, 8'h41, 8'h3F, 8'h01}; // 74 J
mem[ 75] = {8'h7F, 8'h08, 8'h14, 8'h22, 8'h41}; // 75 K
mem[ 76] = {8'h7F, 8'h40, 8'h40, 8'h40, 8'h40}; // 76 L
mem[ 77] = {8'h7F, 8'h02, 8'h0C, 8'h02, 8'h7F}; // 77 M
mem[ 78] = {8'h7F, 8'h04, 8'h08, 8'h10, 8'h7F}; // 78 N
mem[ 79] = {8'h3E, 8'h41, 8'h41, 8'h41, 8'h3E}; // 79 O
mem[ 80] = {8'h7F, 8'h09, 8'h09, 8'h09, 8'h06}; // 80 P
mem[ 81] = {8'h3E, 8'h41, 8'h51, 8'h21, 8'h5E}; // 81 Q
mem[ 82] = {8'h7F, 8'h09, 8'h19, 8'h29, 8'h46}; // 82 R
mem[ 83] = {8'h46, 8'h49, 8'h49, 8'h49, 8'h31}; // 83 S
mem[ 84] = {8'h01, 8'h01, 8'h7F, 8'h01, 8'h01}; // 84 T
mem[ 85] = {8'h3F, 8'h40, 8'h40, 8'h40, 8'h3F}; // 85 U
mem[ 86] = {8'h1F, 8'h20, 8'h40, 8'h20, 8'h1F}; // 86 V
mem[ 87] = {8'h3F, 8'h40, 8'h38, 8'h40, 8'h3F}; // 87 W
mem[ 88] = {8'h63, 8'h14, 8'h08, 8'h14, 8'h63}; // 88 X
mem[ 89] = {8'h07, 8'h08, 8'h70, 8'h08, 8'h07}; // 89 Y
mem[ 90] = {8'h61, 8'h51, 8'h49, 8'h45, 8'h43}; // 90 Z
mem[ 91] = {8'h00, 8'h7F, 8'h41, 8'h41, 8'h00}; // 91 [
mem[ 92] = {8'h55, 8'h2A, 8'h55, 8'h2A, 8'h55}; // 92 .
mem[ 93] = {8'h00, 8'h41, 8'h41, 8'h7F, 8'h00}; // 93 ]
mem[ 94] = {8'h04, 8'h02, 8'h01, 8'h02, 8'h04}; // 94 ^
mem[ 95] = {8'h40, 8'h40, 8'h40, 8'h40, 8'h40}; // 95 _
mem[ 96] = {8'h00, 8'h01, 8'h02, 8'h04, 8'h00}; // 96 '
mem[ 97] = {8'h20, 8'h54, 8'h54, 8'h54, 8'h78}; // 97 a
mem[ 98] = {8'h7F, 8'h48, 8'h44, 8'h44, 8'h38}; // 98 b
mem[ 99] = {8'h38, 8'h44, 8'h44, 8'h44, 8'h20}; // 99 c
mem[100] = {8'h38, 8'h44, 8'h44, 8'h48, 8'h7F}; // 100 d
mem[101] = {8'h38, 8'h54, 8'h54, 8'h54, 8'h18}; // 101 e
mem[102] = {8'h08, 8'h7E, 8'h09, 8'h01, 8'h02}; // 102 f
mem[103] = {8'h18, 8'hA4, 8'hA4, 8'hA4, 8'h7C}; // 103 g
mem[104] = {8'h7F, 8'h08, 8'h04, 8'h04, 8'h78}; // 104 h
mem[105] = {8'h00, 8'h44, 8'h7D, 8'h40, 8'h00}; // 105 i
mem[106] = {8'h40, 8'h80, 8'h84, 8'h7D, 8'h00}; // 106 j
mem[107] = {8'h7F, 8'h10, 8'h28, 8'h44, 8'h00}; // 107 k
mem[108] = {8'h00, 8'h41, 8'h7F, 8'h40, 8'h00}; // 108 l
mem[109] = {8'h7C, 8'h04, 8'h18, 8'h04, 8'h78}; // 109 m
mem[110] = {8'h7C, 8'h08, 8'h04, 8'h04, 8'h78}; // 110 n
mem[111] = {8'h38, 8'h44, 8'h44, 8'h44, 8'h38}; // 111 o
mem[112] = {8'hFC, 8'h24, 8'h24, 8'h24, 8'h18}; // 112 p
mem[113] = {8'h18, 8'h24, 8'h24, 8'h18, 8'hFC}; // 113 q
mem[114] = {8'h7C, 8'h08, 8'h04, 8'h04, 8'h08}; // 114 r
mem[115] = {8'h48, 8'h54, 8'h54, 8'h54, 8'h20}; // 115 s
mem[116] = {8'h04, 8'h3F, 8'h44, 8'h40, 8'h20}; // 116 t
mem[117] = {8'h3C, 8'h40, 8'h40, 8'h20, 8'h7C}; // 117 u
mem[118] = {8'h1C, 8'h20, 8'h40, 8'h20, 8'h1C}; // 118 v
mem[119] = {8'h3C, 8'h40, 8'h30, 8'h40, 8'h3C}; // 119 w
mem[120] = {8'h44, 8'h28, 8'h10, 8'h28, 8'h44}; // 120 x
mem[121] = {8'h1C, 8'hA0, 8'hA0, 8'hA0, 8'h7C}; // 121 y
mem[122] = {8'h44, 8'h64, 8'h54, 8'h4C, 8'h44}; // 122 z
end
endmodule
例化部分
adc和oled讲完了,接下来重中之重就是例化,因为之前有一点点程序基础,思想上想要把每个模块都写到.c文件里然后在main()函数中一起调用或写个循环,在这里一直没弄明白,最后靠万能的百度学习了一下,发现FPGA的例化其实就是上面说的东西。不过不同的是,这里的例化还有一个重要的功能,那就是将每个.v文件的输入和输出连接起来,构成整个工程的输入和输出,将每个.v文件看做一个黑盒,这个和c语言有着本质上的区别。c语言的逻辑是调用每个子程序,按照设计的逻辑一步一步走下去,而FPGA则是每部分就直接是设计好的电路,上电后直接开始运转,每一部分都有自己的输入输出逻辑,顶层例化就是每一部分之间的纽带,每一部分都是基于一个总时钟同步运转。
顶层例化代码也给出
module main(clk,rst_n,adc_dat,adc_cs,adc_clk,oled_csn,oled_rst,oled_dcn,oled_clk,oled_dat);
// 端口声明
input clk, //系统时钟
rst_n; //系统复位,低有效
input adc_dat; //SPI总线SDA
output adc_cs, //SPI总线CS
adc_clk, //SPI总线SCK
oled_csn, //OLCD液晶屏使能
oled_rst, //OLCD液晶屏复位
oled_dcn, //OLCD数据指令控制
oled_clk, //OLCD时钟信号
oled_dat; //OLCD数据信号
// 声明导线
wire adc_done;
wire [15:0] bcd_code;
// 调用(实例引用)较低层次的模块
// 注意它们之间互相交叉连接的情况
adc u1(
.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采样完成标志
.bcd_code (bcd_code) //ADC显示数据
);
oled u2(
.clk (clk), //12MHz系统时钟
.rst_n (rst_n), //系统复位,低有效
.bcd_code (bcd_code), // ADC显示数据
.adc_done (adc_done), // 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数据信号
);
// 模块语句结束
endmodule
寄语
最后,感谢电子森林提供的这次学习FPGA的机会,希望越办越好!