2021暑假在家练项目——用小脚丫FPGA综合技能训练平台,制作一个数字电压表
经过一个月的学习,基于小脚丫FPGA的综合技能训练平台,利用串行ADC制作一个数字电压表,并在OLED屏幕上进行显示得到的数据
标签
FPGA
数字逻辑
显示
Zootopia
更新2021-09-09
1100

上面的视频不要看了,没有什么内容,纯纯的社死现场

一、项目要求 

利用ADC制作一个数字电压表

  1. 旋转电位计可以产生0-3.3V的电压
  2. 利用板上的串行ADC对电压进行转换
  3. 将电压值在板上的OLED屏幕上显示出来

二、系统设计及框图

Fg92iqz1PwblJHKuXbWuVhd7YDSP

模块功能:

pll_div_4:对时钟4分频,以满足ads7868的时序要求

ads7868:控制芯片输出数据,将8位原始数据直接输出到LED灯,转换成电压后再转换为16bcd输出

OLED12832:控制OLED屏幕显示,把输入的四位bcd码转换为带有小数点的数字并输出

 

模块是根据驱动的硬件区分的,而各个模块的子功能则使用状态机实现,一个状态对应一个子功能

1.ads7868

IDLE:初始化变量

CONVERT:模拟SPI时序(代码上看是这样,实际上是硬件电路搭建的),实现原始数据读入。

OUTPUT:原始数据直接输出到LED,数据转换为电压后转换为bcd码输出到OLED模块。

2.OLED12832

IDLE:初始化变量,转入INIT状态。

MAIN:根据要显示的内容修改内部变量数据。

INIT:按顺序写入初始化命令。

SCAN:根据现在的变量数据扫描一行(8像素)OLED

WRITE:生成spi时序,向OLED发送命令或数据

DELAY:延时,在某一命令需要较长执行时间的时候需要延时,供其他状态调用

三、基本电路分析

输出电路为纯数字接口电路,不过多介绍
输入端电路:

 

由图可知,输入端电压在0V到3.3V之间,ADS7868供电电压3.3V,这些在下面分析中会用到

ADS7868芯片分析:

以下对芯片的分析均来自官方数据表:

https://www.ti.com.cn/cn/lit/ds/symlink/ads7868.pdf?ts=1630151001066&ref_url=https%253A%252F%252Fwww.ti.com.cn%252Fproduct%252Fcn%252FADS7868

手册包含ADS7866、ADS7867、ADS7868三种型号的芯片,它们的输出位数不同,转换时间不同,查阅数据时注意区分。

首先,由电路图和手册都能判断ADS7868使用SPI协议通讯。那么就需要知道以下3点信息才能正确使用它:

1.时序要求

2.芯片没有数据输入(SDI),如何控制芯片在合适时间正确输出数据

3.输出数据的格式

1.过快或过慢的时钟信号都不能让芯片正常工作

先说结论:3.3V电压下,150kHZ<=fclk<= 3.4MHz(着急的不用往下看了,跳过这一部分就好)

下面是找手册的过程,平时不怎么读手册的我。。。有点痛苦。

手册第9页给出了时序要求

Fu4kH9dWUCNIJKAVNdjF77pepMS5

给了个最大时钟周期,换算一下大概是150kHz,也就是说时钟频率最低150kHz。

最小时钟周期给了个注释,来到注释的地方如下

FnaUS_hlnb8-43w_hCe_S30THSDs

不得不说,这个芯片的时钟要求挺绕的,好在下一页给了结论

FqyPkMiK-L7qGkr3MxcfXPDNnGEP

在芯片手册18页,TI已经为我们贴心的算好了时钟最大频率,供电电压3.3V情况下最大频率为3.4MHz(多出来的那些3.4MHz是其他芯片型号的)。

2.芯片控制方法???(标题改一下)

学过单片机都知道,SPI有4种模式,对应不同的时钟极性和时钟相位(没学过的就当是学过了吧)

还是先说结论:直接看第九页时序图

SPI模式3,(CPOL=1,CPHA=1)时钟无效时为高电平,时钟下降沿改变数据,上升沿采集数据(此时数据有效)

CS(片选)低电平有效,CS拉低,此时SDO也随之变为低电平,在接下来第四个时钟下降沿开始传输8位有效数据(从高到低),传输完成后应该及时将CS拉高。

FnDr4fXV09faN4NC_g9Yh7hN3SRe

接下来是痛苦的查阅手册过程总结:
首先时序图不是一眼就能看懂的,看19页DIGITAL INTERFACE详细的解释了它的工作流程,可以说把这个时序图解释的相当清楚了,当然原文是英文,机翻如下(感谢谷歌翻译娘):

ADS7866/67/68 通过高速 SPI 兼容串行接口与微处理器或 DSP 接口,CPOL = 1(无效 SCLK 返回逻辑高或 SCLK 前沿为上升沿),CPHA = 1(输出数据在下降时发生变化) SCLK 的上升沿,并且在 SCLK 的上升沿可用)。 SDO 的采样、转换和激活在 CS 的下降沿启动。串行时钟 (SCLK) 用于控制转换速率。它还提供了一种允许与数字主机处理器同步的机制。

数字输入 CS 和 SCLK 可以超过电源电压 V DD,只要它们不超过 3.6 V 的最大 V IH。这允许 ADS7866/67/68 系列与使用不同电源电压的主机处理器连接比转换器无需外部电平转换电路。此外,数字输入可以在转换器的电源电压激活之前应用于 CS 和 SCLK,而不会产生闩锁条件的风险。

ADS7866/67/68 分别输出 4 个前导零后的 12/10/8 位数据。这些代码是直接二进制格式,如表 2 所示。

串行输出 SDO 在 CS 的下降沿激活。第一个前导零在 SDO 上可用,直到 CS 下降后 SCLK 的第一个下降沿。 CS 下降后,其余 3 个前导零在 SCLK 的第一个、第二个和第三个下降沿移出 SDO。转换结果的 MSB 跟随 4 个前导零,并在 SCLK 的第四个下降沿输出。当 EOC 发生时,CS 的上升沿或 SCLK 的下降沿将 SDO 输出置于三态。有关理想输出代码与输入电压的关系,请参阅表 2。

Fm9g70hbdWzbGmSfHbfXMC8ZpWOu

表2描述了数据输出格式。ADS7868输出一个8位二进制数(0-255),代表一个从0到VDD-1LSB的电压。值得注意的是,它的最大输出电压不是VDD,而是255*VDD/256。VDD是电源电压,对3.3V最小精度单位为3.3/256=0.012890625V,取0.0129。那么它测得的最大电压就是3.2895V。

 

四、代码设计
 

代码编写分为2个模块

1.ADS7868驱动及数制转换(二进制到十进制)

开发板时钟是12MHz的,对于ADS7868来说太高了,需要降低时钟频率,可以使用手搓的分频器或者锁相环ip核实现。这里使用了ip核,因为它频率准确,配置简单。使用Diamond的IPexpress可以傻瓜式配置,做的时候忘截图了。不知道是操作有误还是软件bug,生成的文件中部分变量值异常导致综合无法通过,那些变量都是锁相环未使用的功能中用到的变量,都改成0就正常了。大概集中在这一部分,这是改过之后的,改之前的忘了截图了。

FgVV0yBuofY7Mq3h_rjIZR7K0irl

然后就是spi的实现了,参照OLED显示例程,发现它的实现与单片机软件SPI十分相似,资料不够丰富的情况下完全可以借鉴。使用状态机完成准备状态、读数据状态、数据处理状态的切换,使用计数器和case语句实现程序逐步执行。

代码如下:

module ads7868
(
input     wire   sys_clk,   //input clk 12MHz
input     wire   sys_rst_n,
input     wire   data_in,

output    reg    adc_clk,   //output clk 3MHz
output    reg    adc_cs_n,
output    reg    [7:0]led_out,
output    reg    [15:0]bcd_out


);

	localparam IDLE = 3'h1, CONVERT = 3'h2, OUTPUT = 3'h4;//状态机:独热码表示状态
	localparam HIGH	= 1'b1, LOW = 1'b0;
	localparam CNT_TIME=8'h05;
	localparam VDD=8'd129;//VDD约等于0.00129V,用整数表示便于计算
wire          sys_clk_div4;

reg    [15:0]bcd_out;

reg   [5:0]   state;
reg   [3:0]   cnt_cycle;
reg   [4:0]   cnt_step;
reg   [4:0]   cnt_step_out;
reg   [4:0]   cnt_shift;
reg   [7:0]   adc_data;


reg   [35:0]  shift;//移位二进制转bcd




pll_div4 pll_div4_inst (.CLKI(sys_clk), .CLKOP(sys_clk_div4));//调用锁相环ip对时钟四分频(PLL)

always@(posedge sys_clk_div4 or negedge sys_rst_n)
begin
     if(sys_rst_n==1'b0)begin
		adc_clk<=HIGH;
		adc_cs_n<=HIGH;
		state<=IDLE;
		led_out<=8'b11111111;
		end
	else begin
	
	case (state)
	IDLE://重置所有变量
	begin
	    cnt_step<=1'b0;cnt_step_out<=0;cnt_shift<=0;
		shift<=1'b0;
		adc_clk<=HIGH;
		adc_cs_n<=HIGH;
		state<=CONVERT;
	end
	CONVERT:
		begin 
		if(cnt_step>=5'd24)cnt_step<=1'b0;
		else cnt_step <= cnt_step +1'b1 ;
		case(cnt_step)
			5'd0 :begin adc_cs_n<=LOW;    end//使能ADC
			5'd1 :begin adc_clk<=LOW;     end//等待4个下降沿
			5'd2 :begin adc_clk<=HIGH;     end
			5'd3 :begin adc_clk<=LOW;     end
			5'd4 :begin adc_clk<=HIGH;     end
			5'd5 :begin adc_clk<=LOW;     end
			5'd6 :begin adc_clk<=HIGH;     end
			5'd7 :begin adc_clk<=LOW;     end
			5'd8 :begin adc_clk<=HIGH; adc_data[7]<=data_in;     end
			5'd9 :begin adc_clk<=LOW;     end
			5'd10:begin adc_clk<=HIGH; adc_data[6]<=data_in;     end
			5'd11:begin adc_clk<=LOW;     end
			5'd12:begin adc_clk<=HIGH; adc_data[5]<=data_in;     end
			5'd13:begin adc_clk<=LOW;     end
			5'd14:begin adc_clk<=HIGH; adc_data[4]<=data_in;     end
			5'd15:begin adc_clk<=LOW;     end
			5'd16:begin adc_clk<=HIGH; adc_data[3]<=data_in;    end
			5'd17:begin adc_clk<=LOW;     end
			5'd18:begin adc_clk<=HIGH; adc_data[2]<=data_in;     end
			5'd19:begin adc_clk<=LOW;     end
			5'd20:begin adc_clk<=HIGH; adc_data[1]<=data_in;     end
			5'd21:begin adc_clk<=LOW;     end
			5'd22:begin adc_clk<=HIGH; adc_data[0]<=data_in;     end
			5'd23:begin adc_cs_n<=HIGH;     end
			5'd24:begin state<=OUTPUT; shift[15:0]<=adc_data*VDD;     end
			default:begin state<=IDLE; end
		endcase
		end
OUTPUT:
		begin
		//led_out<=adc_data;
		
		//bcd_out<=shift[20:5];
		if(cnt_step_out>=5'd4)cnt_step_out<=1'b0;
		else cnt_step_out <= cnt_step_out + 1'b1;
		if(cnt_shift>=5'd16)begin cnt_shift<=1'b0; state<=IDLE;bcd_out<=shift[35:20]; end
		else cnt_shift<=cnt_shift;
		case(cnt_step_out)

		5'd0:begin shift<=shift<<1;cnt_shift<=cnt_shift+1'b1;led_out<=adc_data;end
		5'd1:begin if(shift[19:16]>=4'd5)shift[19:16]<=shift[19:16]+2'd11;else shift<=shift; end
		5'd2:begin if(shift[23:20]>=4'd5)shift[23:20]<=shift[23:20]+2'd11;else shift<=shift; end
		5'd3:begin if(shift[27:24]>=4'd5)shift[27:24]<=shift[27:24]+2'd11;else shift<=shift; end
		5'd4:begin if(shift[31:28]>=4'd5)shift[31:28]<=shift[31:28]+2'd11;else shift<=shift; end//最高位不可能大于5

		default:state<=IDLE;
		
		
		
		endcase
		end
		
	default:state<=IDLE;
	endcase
end
end

endmodule

IDLE:初始化(如果下面逻辑严谨可以没有这个状态)

CONVERT:模拟SPI时序(代码上看是这样,实际上是硬件电路搭建的),实现原始数据读入。

OUTPUT:数据直接输出到LED,转换为电压后转换为bcd输出到OLED模块。

转换算法来自于https://zhuanlan.zhihu.com/p/209083141#:~:text=%E4%BA%8C%E8%BF%9B%E5%88%B6%E8%BD%ACBCD%E7%A0%81,%E8%BF%9B%E5%88%B6%E8%BD%ACBCD%E7%A0%81%E3%80%82

的前半部分,这是一个移位加三的算法,为了简化代码,不区分情况的对各个位都进行判断,这段时间正好让ADC休息一下(两次转换之间需要一些时间)。

OLED显示

主要参照https://www.eetree.cn/wiki/oled_spi_verilog以及https://blog.csdn.net/qq_39829913/article/details/104716829,感谢硬禾学堂和这位大佬所做的总结,我对源代码添加了一些注释,希望能够更通俗易懂。

 

// --------------------------------------------------------------------
// >>>>>>>>>>>>>>>>>>>>>>>>> COPYRIGHT NOTICE <<<<<<<<<<<<<<<<<<<<<<<<<
// --------------------------------------------------------------------
// Module: OLED12832
// 
// Author: Step
// 
// Description: OLED12832_Driver,使用8*8点阵字库,每行显示128/8=16个字符
// 
// Web: www.stepfpga.com
// 
// --------------------------------------------------------------------
// Code Revision History :
// --------------------------------------------------------------------
// Version: |Mod. Date:   |Changes Made:
// V1.0     |2015/11/11   |Initial ver
// --------------------------------------------------------------------
module OLED12832
(
	input				clk,		//12MHz系统时钟
	input				rst_n,		//系统复位,低有效
 
	input		[15:0]	bcd_in,		//数据输入

 
	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;                            //dc的高低电平表示传输数据还是命令

	reg [7:0] cmd [24:0];                                          //存储命令
	reg [39:0] mem [122:0];                                        //存储点阵字符数据
	reg	[7:0]	y_p, x_ph, x_pl;                                   //坐标,y坐标1字节,x坐标2字节 h.l表示高低位
	reg	[(8*21-1):0] char;                                         //21个ASCII字符缓存
	reg	[7:0]	num, char_reg;				                       //num为剩余字符数,char_reg为输出缓存
	reg	[4:0]	cnt_main, cnt_init, cnt_scan, cnt_write;           //计数器,用于控制逐步实现spi通讯
	reg	[15:0]	num_delay, cnt_delay, cnt;                         //前两个计数器变量用于延迟,其中cnt_delay自增,设置num_delay改变延迟的时间,cnt用于初始化指令计数
	reg	[5:0] 	state, state_back;                                 //改变时分别表示要切换到的状态,以及现在的状态,以供返回
 
	always@(posedge clk 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;//片选关,spi模式3,与ads7868相同
						state <= MAIN; state_back <= MAIN;//进入MAIN状态
					end
				MAIN:begin
						if(cnt_main >= 5'd5) 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 <= "Voltage :       ";state <= SCAN; end//写完数据跳转到SCAN状态
							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'd6; char <= {4'h0,bcd_in[15:12],".",4'h0,bcd_in[11:8],4'h0,bcd_in[7:4],4'h0,bcd_in[3:0],"V"}; state <= SCAN; end
							//5'd6:	begin y_p <= 8'hb1; x_ph <= 8'h15; x_pl <= 8'h00; num <= 5'd 1; state <= SCAN; end
							//5'd7:	begin y_p <= 8'hb2; x_ph <= 8'h15; x_pl <= 8'h00; num <= 5'd 1; state <= SCAN; end
							//5'd8:	begin y_p <= 8'hb3; x_ph <= 8'h15; x_pl <= 8'h00; num <= 5'd 1; 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配置指令数据,在初始化化OLED时逐条发送给OLED
	always@(posedge rst_n)
		begin
			cmd[ 0] = {8'hae}; //关闭OLED
			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
		
		//0-F常用字符,单独存储好处是便于数字显示的实现,无需转换为ASCII
			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
			
 		//ASCII字符,序号与ASCII一致,好处是便于字符串显示的实现
			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

 

最后,两个模块合并一下,记得在Diamond中设置顶层模块,代码就不贴了,见附件。

 

下一步进行引脚分配。这里推荐一下小脚丫FPGA官网https://www.stepfpga.com/

对小脚丫系列开发板,里面的引脚分配说明十分详细,而且配有图示,比起原理图和表格要方便不少。

FgWwMwb953_Od2P2_s3e4Mwl_xHK

 

五、项目总结

    其实有很多想说的,但是本人太懒,就写一点吧。这次活动学到了很多知识。Verilog语言十分简单,但是如何把它运用实现自己想要的效果一点都不简单。这个简单的小项目也用了我不少时间查阅资料、学习别人的代码,学会了很多奇妙的设计方法。本来的想法是先搞定项目1入门,再完成项目四,可惜出了很多玄学问题,未能如愿,没能完成项目4很遗憾。

  初学FPGA,第一次用Diamond,搞不清楚里面的文件都是做什么的,所以全部打包了,文件结构也比较混乱,还望谅解。

附项目结果图:

FkY1j8uHJWo-so3jYilv73DoCc-h

 

 

 

附件下载
工程.zip
整个工程打包
输出文件.jed
用于烧录的文件,如果和我用的是同一款板卡,复制到FPGA即可
团队介绍
华工电子小檬新一枚~
团队成员
Zootopia
评论
0 / 100
查看更多
目录
硬禾服务号
关注最新动态
0512-67862536
info@eetree.cn
江苏省苏州市苏州工业园区新平街388号腾飞创新园A2幢815室
苏州硬禾信息科技有限公司
Copyright © 2023 苏州硬禾信息科技有限公司 All Rights Reserved 苏ICP备19040198号