目录
一、功能描述
最近设计一款FPGA实现的具有称重、去皮、计价、报警功能的数字电子秤,分享如下。设计使用野火征途Pro系列开发板。称重范围设定0~200g,超过该范围蜂鸣器触发报警。称重传感器种类繁多,但输出信号相似,输出与称重线性的电压,因此本设计均适用,只需稍作修改即可。设计采用CSY2000系列传感器获取质量信号,其输出电压与称重质量成线性关系。
二、顶层设计分析
顶层文件看着繁琐复杂,实际每个模块的代码实现起来很简单,大家可以结合后面的子模块分析。
系统输入FPGA的信号有系统时钟sys_clk、系统复位sys_rst_n以及三个按键输入信号qp、cf、ok,依次代表去皮,计价,确定按键,具体在后面子模块解释。
系统输出有与AD模块PCF8591T进行I2c通信的i2c_scl、i2c_sda,输出给数码管显示数据的ds,oe,shcp,stcp,输出给蜂鸣器的报警信号buff。
在看代码之前,我在这备注下此设计中计价和价格区别,防止大家记混,本文计价过程是设定称重物品多少钱一克,而价格过程是输出当前质量物品在设置计价后的价格。
2.1 I2c_ctrl模块
PCF8591T模块采用I2c协议通信,所以我们需要一个I2c_ctrl模块来实现该通信协议,具体代码引用野火征途系列开发板官方代码如下
`timescale 1ns/1ns
// Author : EmbedFire
// Create Date : 2019/04/01
// Module Name : i2c_ctrl
// Project Name : ad
// Target Devices: Altera EP4CE10F17C8N
// Tool Versions : Quartus 13.0
// Description : i2c_ctrl
//
// Revision : V1.0
// Additional Comments:
//
// 实验平台: 野火_征途Pro_FPGA开发板
// 公司 : http://www.embedfire.com
// 论坛 : http://www.firebbs.cn
// 淘宝 : https://fire-stm32.taobao.com
module i2c_ctrl
#(
parameter DEVICE_ADDR = 7'b1010_000 , //i2c设备地址
parameter SYS_CLK_FREQ = 26'd50_000_000 , //输入系统时钟频率
parameter SCL_FREQ = 18'd250_000 //i2c设备scl时钟频率
)
(
input wire sys_clk , //输入系统时钟,50MHz
input wire sys_rst_n , //输入复位信号,低电平有效
input wire wr_en , //输入写使能信号
input wire rd_en , //输入读使能信号
input wire i2c_start , //输入i2c触发信号
input wire addr_num , //输入i2c字节地址字节数
input wire [15:0] byte_addr , //输入i2c字节地址
input wire [7:0] wr_data , //输入i2c设备数据
output reg i2c_clk , //i2c驱动时钟
output reg i2c_end , //i2c一次读/写操作完成
output reg [7:0] rd_data , //输出i2c设备读取数据
output reg i2c_scl , //输出至i2c设备的串行时钟信号scl
inout wire i2c_sda //输出至i2c设备的串行数据信号sda
);
//************************************************************************//
//******************** Parameter and Internal Signal *********************//
//************************************************************************//
// parameter define
localparam CNT_CLK_MAX = (SYS_CLK_FREQ/SCL_FREQ) >> 2'd3 ; //cnt_clk计数器计数最大值
localparam CNT_START_MAX = 8'd100; //cnt_start计数器计数最大值
localparam IDLE = 4'd00, //初始状态
START_1 = 4'd01, //开始状态1
SEND_D_ADDR = 4'd02, //设备地址写入状态 + 控制写
ACK_1 = 4'd03, //应答状态1
SEND_B_ADDR_H = 4'd04, //字节地址高八位写入状态
ACK_2 = 4'd05, //应答状态2
SEND_B_ADDR_L = 4'd06, //字节地址低八位写入状态
ACK_3 = 4'd07, //应答状态3
WR_DATA = 4'd08, //写数据状态
ACK_4 = 4'd09, //应答状态4
START_2 = 4'd10, //开始状态2
SEND_RD_ADDR = 4'd11, //设备地址写入状态 + 控制读
ACK_5 = 4'd12, //应答状态5
RD_DATA = 4'd13, //读数据状态
N_ACK = 4'd14, //非应答状态
STOP = 4'd15; //结束状态
// wire define
wire sda_in ; //sda输入数据寄存
wire sda_en ; //sda数据写入使能信号
// reg define
reg [7:0] cnt_clk ; //系统时钟计数器,控制生成clk_i2c时钟信号
reg [3:0] state ; //状态机状态
reg cnt_i2c_clk_en ; //cnt_i2c_clk计数器使能信号
reg [1:0] cnt_i2c_clk ; //clk_i2c时钟计数器,控制生成cnt_bit信号
reg [2:0] cnt_bit ; //sda比特计数器
reg ack ; //应答信号
reg i2c_sda_reg ; //sda数据缓存
reg [7:0] rd_data_reg ; //自i2c设备读出数据
//************************************************************************//
//******************************* Main Code ******************************//
//************************************************************************//
// cnt_clk:系统时钟计数器,控制生成clk_i2c时钟信号
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
cnt_clk <= 8'd0;
else if(cnt_clk == CNT_CLK_MAX - 1'b1)
cnt_clk <= 8'd0;
else
cnt_clk <= cnt_clk + 1'b1;
// i2c_clk:i2c驱动时钟
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
i2c_clk <= 1'b1;
else if(cnt_clk == CNT_CLK_MAX - 1'b1)
i2c_clk <= ~i2c_clk;
// cnt_i2c_clk_en:cnt_i2c_clk计数器使能信号
always@(posedge i2c_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
cnt_i2c_clk_en <= 1'b0;
else if((state == STOP) && (cnt_bit == 3'd3) &&(cnt_i2c_clk == 3))
cnt_i2c_clk_en <= 1'b0;
else if(i2c_start == 1'b1)
cnt_i2c_clk_en <= 1'b1;
// cnt_i2c_clk:i2c_clk时钟计数器,控制生成cnt_bit信号
always@(posedge i2c_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
cnt_i2c_clk <= 2'd0;
else if(cnt_i2c_clk_en == 1'b1)
cnt_i2c_clk <= cnt_i2c_clk + 1'b1;
// cnt_bit:sda比特计数器
always@(posedge i2c_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
cnt_bit <= 3'd0;
else if((state == IDLE) || (state == START_1) || (state == START_2)
|| (state == ACK_1) || (state == ACK_2) || (state == ACK_3)
|| (state == ACK_4) || (state == ACK_5) || (state == N_ACK))
cnt_bit <= 3'd0;
else if((cnt_bit == 3'd7) && (cnt_i2c_clk == 2'd3))
cnt_bit <= 3'd0;
else if((cnt_i2c_clk == 2'd3) && (state != IDLE))
cnt_bit <= cnt_bit + 1'b1;
// state:状态机状态跳转
always@(posedge i2c_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
state <= IDLE;
else case(state)
IDLE:
if(i2c_start == 1'b1)
state <= START_1;
else
state <= state;
START_1:
if(cnt_i2c_clk == 3)
state <= SEND_D_ADDR;
else
state <= state;
SEND_D_ADDR:
if((cnt_bit == 3'd7) &&(cnt_i2c_clk == 3))
state <= ACK_1;
else
state <= state;
ACK_1:
if((cnt_i2c_clk == 3) && (ack == 1'b0))
begin
if(addr_num == 1'b1)
state <= SEND_B_ADDR_H;
else
state <= SEND_B_ADDR_L;
end
else
state <= state;
SEND_B_ADDR_H:
if((cnt_bit == 3'd7) &&(cnt_i2c_clk == 3))
state <= ACK_2;
else
state <= state;
ACK_2:
if((cnt_i2c_clk == 3) && (ack == 1'b0))
state <= SEND_B_ADDR_L;
else
state <= state;
SEND_B_ADDR_L:
if((cnt_bit == 3'd7) && (cnt_i2c_clk == 3))
state <= ACK_3;
else
state <= state;
ACK_3:
if((cnt_i2c_clk == 3) && (ack == 1'b0))
begin
if(wr_en == 1'b1)
state <= WR_DATA;
else if(rd_en == 1'b1)
state <= START_2;
else
state <= state;
end
else
state <= state;
WR_DATA:
if((cnt_bit == 3'd7) &&(cnt_i2c_clk == 3))
state <= ACK_4;
else
state <= state;
ACK_4:
if((cnt_i2c_clk == 3) && (ack == 1'b0))
state <= STOP;
else
state <= state;
START_2:
if(cnt_i2c_clk == 3)
state <= SEND_RD_ADDR;
else
state <= state;
SEND_RD_ADDR:
if((cnt_bit == 3'd7) &&(cnt_i2c_clk == 3))
state <= ACK_5;
else
state <= state;
ACK_5:
if((cnt_i2c_clk == 3) && (ack == 1'b0))
state <= RD_DATA;
else
state <= state;
RD_DATA:
if((cnt_bit == 3'd7) &&(cnt_i2c_clk == 3))
state <= N_ACK;
else
state <= state;
N_ACK:
if(cnt_i2c_clk == 3)
state <= STOP;
else
state <= state;
STOP:
if((cnt_bit == 3'd3) &&(cnt_i2c_clk == 3))
state <= IDLE;
else
state <= state;
default: state <= IDLE;
endcase
// ack:应答信号
always@(*)
case (state)
IDLE,START_1,SEND_D_ADDR,SEND_B_ADDR_H,SEND_B_ADDR_L,
WR_DATA,START_2,SEND_RD_ADDR,RD_DATA,N_ACK:
ack <= 1'b1;
ACK_1,ACK_2,ACK_3,ACK_4,ACK_5:
if(cnt_i2c_clk == 2'd0)
ack <= sda_in;
else
ack <= ack;
default: ack <= 1'b1;
endcase
// i2c_scl:输出至i2c设备的串行时钟信号scl
always@(*)
case (state)
IDLE:
i2c_scl <= 1'b1;
START_1:
if(cnt_i2c_clk == 2'd3)
i2c_scl <= 1'b0;
else
i2c_scl <= 1'b1;
SEND_D_ADDR,ACK_1,SEND_B_ADDR_H,ACK_2,SEND_B_ADDR_L,
ACK_3,WR_DATA,ACK_4,START_2,SEND_RD_ADDR,ACK_5,RD_DATA,N_ACK:
if((cnt_i2c_clk == 2'd1) || (cnt_i2c_clk == 2'd2))
i2c_scl <= 1'b1;
else
i2c_scl <= 1'b0;
STOP:
if((cnt_bit == 3'd0) &&(cnt_i2c_clk == 2'd0))
i2c_scl <= 1'b0;
else
i2c_scl <= 1'b1;
default: i2c_scl <= 1'b1;
endcase
// i2c_sda_reg:sda数据缓存
always@(*)
case (state)
IDLE:
begin
i2c_sda_reg <= 1'b1;
rd_data_reg <= 8'd0;
end
START_1:
if(cnt_i2c_clk <= 2'd0)
i2c_sda_reg <= 1'b1;
else
i2c_sda_reg <= 1'b0;
SEND_D_ADDR:
if(cnt_bit <= 3'd6)
i2c_sda_reg <= DEVICE_ADDR[6 - cnt_bit];
else
i2c_sda_reg <= 1'b0;
ACK_1:
i2c_sda_reg <= 1'b1;
SEND_B_ADDR_H:
i2c_sda_reg <= byte_addr[15 - cnt_bit];
ACK_2:
i2c_sda_reg <= 1'b1;
SEND_B_ADDR_L:
i2c_sda_reg <= byte_addr[7 - cnt_bit];
ACK_3:
i2c_sda_reg <= 1'b1;
WR_DATA:
i2c_sda_reg <= wr_data[7 - cnt_bit];
ACK_4:
i2c_sda_reg <= 1'b1;
START_2:
if(cnt_i2c_clk <= 2'd1)
i2c_sda_reg <= 1'b1;
else
i2c_sda_reg <= 1'b0;
SEND_RD_ADDR:
if(cnt_bit <= 3'd6)
i2c_sda_reg <= DEVICE_ADDR[6 - cnt_bit];
else
i2c_sda_reg <= 1'b1;
ACK_5:
i2c_sda_reg <= 1'b1;
RD_DATA:
if(cnt_i2c_clk == 2'd2)
rd_data_reg[7 - cnt_bit] <= sda_in;
else
rd_data_reg <= rd_data_reg;
N_ACK:
i2c_sda_reg <= 1'b1;
STOP:
if((cnt_bit == 3'd0) && (cnt_i2c_clk < 2'd3))
i2c_sda_reg <= 1'b0;
else
i2c_sda_reg <= 1'b1;
default:
begin
i2c_sda_reg <= 1'b1;
rd_data_reg <= rd_data_reg;
end
endcase
// rd_data:自i2c设备读出数据
always@(posedge i2c_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
rd_data <= 8'd0;
else if((state == RD_DATA) && (cnt_bit == 3'd7) && (cnt_i2c_clk == 2'd3))
rd_data <= rd_data_reg;
// i2c_end:一次读/写结束信号
always@(posedge i2c_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
i2c_end <= 1'b0;
else if((state == STOP) && (cnt_bit == 3'd3) &&(cnt_i2c_clk == 3))
i2c_end <= 1'b1;
else
i2c_end <= 1'b0;
// sda_in:sda输入数据寄存
assign sda_in = i2c_sda;
// sda_en:sda数据写入使能信号
assign sda_en = ((state == RD_DATA) || (state == ACK_1) || (state == ACK_2)
|| (state == ACK_3) || (state == ACK_4) || (state == ACK_5))
? 1'b0 : 1'b1;
// i2c_sda:输出至i2c设备的串行数据信号sda
assign i2c_sda = (sda_en == 1'b1) ? i2c_sda_reg : 1'bz;
endmodule
2.2 PCF8591_ad模块
PCF8591接线如下
此模块负责控制PCF8591模块输出采样后数据,这里引用野火FPGA征途Pro代码,需要注意的是我们这里要从外部获取电压信号进行ad转换,所以模拟输入通道要选择IN1-3,即AD/DA控制字要为010000101
`timescale 1ns/1ns
// Author : EmbedFire
// Create Date : 2019/04/01
// Module Name : pcf8591_adda
// Project Name : ad
// Target Devices: Altera EP4CE10F17C8N
// Tool Versions : Quartus 13.0
// Description : AD电压测量模块
//
// Revision : V1.0
// Additional Comments:
//
// 实验平台: 野火_征途Pro_FPGA开发板
// 公司 : http://www.embedfire.com
// 论坛 : http://www.firebbs.cn
// 淘宝 : https://fire-stm32.taobao.com
module pcf8591_ad
(
input wire sys_clk , //输入系统时钟,50MHz
input wire sys_rst_n , //输入复位信号,低电平有效
input wire i2c_end , //i2c设备一次读/写操作完成
input wire [7:0] rd_data , //输出i2c设备读取数据
output reg rd_en , //输入i2c设备读使能信号
output reg i2c_start , //输入i2c设备触发信号
output reg [15:0] byte_addr , //输入i2c设备字节地址
output wire [19:0] po_data //数码管待显示数据
);
//************************************************************************//
//******************** Parameter and Internal Signal *********************//
//************************************************************************//
//parameter define
parameter CTRL_DATA = 8'b0100_0001; //AD/DA控制字
parameter CNT_WAIT_MAX= 18'd6_9999 ; //采样间隔计数最大值
parameter IDLE = 3'b001,
AD_START = 3'b010,
AD_CMD = 3'b100;
//wire define
wire [31:0] data_reg/* synthesis keep */; //数码管待显示数据缓存
//reg define
reg [17:0] cnt_wait; //采样间隔计数器
reg [4:0] state ; //状态机状态变量
reg [7:0] ad_data ; //AD数据
//********************************************************************//
//***************************** Main Code ****************************//
//********************************************************************//
//cnt_wait:采样间隔计数器
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
cnt_wait <= 18'd0;
else if(state == IDLE)
if(cnt_wait == CNT_WAIT_MAX)
cnt_wait <= 18'd0;
else
cnt_wait <= cnt_wait + 18'd1;
else
cnt_wait <= 18'd0;
//state:状态机状态变量
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
state <= IDLE;
else
case(state)
IDLE:
if(cnt_wait == CNT_WAIT_MAX)
state <= AD_START;
else
state <= IDLE;
AD_START:
state <= AD_CMD;
AD_CMD:
if(i2c_end == 1'b1)
state <= IDLE;
else
state <= AD_CMD;
default:state <= IDLE;
endcase
//i2c_start:输入i2c设备触发信号
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
i2c_start <= 1'b0;
else if(state == AD_START)
i2c_start <= 1'b1;
else
i2c_start <= 1'b0;
//rd_en:输入i2c设备读使能信号
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
rd_en <= 1'b0;
else if(state == AD_CMD)
rd_en <= 1'b1;
else
rd_en <= 1'b0;
//byte_addr:输入i2c设备字节地址
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
byte_addr <= 16'b0;
else
byte_addr <= CTRL_DATA;
//ad_data:AD数据
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
ad_data <= 8'b0;
else if(i2c_end == 1'b1) //(state == AD_CMD) && (i2c_end == 1'b1))
ad_data <= rd_data;
//data_reg:数码管待显示数据缓存
assign data_reg = ((ad_data * 330) >> 4'd8);//shao0
//po_data:数码管待显示数据
assign po_data = data_reg[19:0];
endmodule
2.3 v_weigh 电压转质量模块
PCF8591T采集的电压值与称重质量成线性,找到比例关系后设计v_weigh模块将电压转换为质量,本设计质量传感器电压输出经外接电压放大器放大后与质量比例系数为6:5,可根据传感器输出关系进行更改。代码实现如下
module v_weigh(
input [19:0]v_data, //输入电压数据
input sys_clk,
input sys_rst_n,
output reg [8:0]g_data //输出9位质量信号
);
always@(posedge sys_clk or negedge sys_rst_n)
if(!sys_rst_n)
g_data<=9'd0; //初始输出信号赋值
else
g_data<=(v_data*5)/6; //电压、质量比例转换
endmodule
2.4 weighing_pre 去皮模块
去皮的具体流程即当qp按键按下时,此时将质量显示归零,相当于剔除秤盘质量,在此为零刻度进行称重。但实际传感器受到的压力仍为秤盘与称重物品质量之和。
module weighing_pre(
input [8:0]weigh, //从转换模块得到的质量数据
input sys_clk,
input sys_rst_n,
input qp, //去皮信号输入
output wire [8:0]now_weigh //去皮后现实的质量
);
reg [8:0]qz; //
//qp去皮信号有效后,将此刻质量赋值qz,表示秤盘质量
always@(posedge sys_clk or negedge sys_rst_n)
if(!sys_rst_n)
qz<=9'd0;
else if(qp)
qz<=weigh;
//组合逻辑,立即赋值,剔除秤盘质量
assign now_weigh=weigh-qz;
endmodule
2.5 mcx 计价模块
征途Pro开发板的外设按键资源有限,这里采用一个按键控制计价的选择,每当按下一次按键,计价数据加1,比如你想要3块钱1克,触发三次按键即可。
module mcx(
input cf, //计价按键输入信号
input sys_clk,
input sys_rst_n,
output reg[6:0]mx //计按下按键次数
);
//每按下一次cf按键,计价数据加1
always@(posedge sys_clk or negedge sys_rst_n)
if(!sys_rst_n)
mx<=7'd0;
else if(cf)
mx<=mx+1'd1;
endmodule
2.6 money 价格输出模块
上个模块给出了称重物品多少钱1克,此模块实现价格的输出
module money(
input sys_clk,
input sys_rst_n,
input ok, //ok按键,当计价选定好后,触发此按键得到价格
input [8:0]now_weigh, //质量输入
input [6:0]mx, //计价输入
output reg[9:0]moy //价格输出
);
//价格计算
always@(posedge sys_clk or negedge sys_rst_n)
if(!sys_rst_n)
moy<=10'd0;
else if(ok)
moy<=now_weigh*mx;
endmodule
2.7 chose 数码管选择显示模块
chose模块主要功能是数码管显示的信号的选择,因为项目涉及数码管显示的数据不唯一,有质量显示、计价显示、价格显示,而数码管有限,所以需要设计一个模块负责控制什么时候数码管显示什么数据。
module chose (
input sys_clk,
input sys_rst_n,
input sign_mo, //价格显示信号
input sign_mx, //计价显示信号
input [9:0]mo, //价格输入
input [6:0]mx, //计价输入
input [8:0]now_weigh, //质量输入
output reg[19:0]data //输出给数码管的数据
);
//这里利用if,else if优先级特性,如果价格显示信号有效,优先显示价格,其次显示计价,最后优先级显示质量
//达到效果:初始计价信号和价格信号均无效,时刻显示质量,接着触发计价按键输入多少钱一克的计价数据,最后触发ok键,因其最高优先级,所以此时模块输出数据为价格数据。利用此模块达到数码管多数据切换利用目的
always@(posedge sys_clk or negedge sys_rst_n)
if(!sys_rst_n)
data<=20'd0;
else if(sign_mo) //如果价格信号有效
data<={10'd0,mo};
else if(sign_mx) //如果计价信号有效
data<={13'd0,mx};
else
data<={11'd0,now_weigh};
endmodule
2.8 sign_give 信号提供模块
上个模块控制数码管显示什么数据,用到了sign_mo、sign_mx信号,实现数据输出何种信号的控制。sign_give模块负责产生上面的两个控制信号。思路也很简单,当ok键按下代表数码管要显示价格,将sign_mo信号拉高,同理,当计价按键按下,sign_mx信号拉高。
module sign_give(
input cf_in, //消抖后的计价按键信号输入
input ok_in, //消抖后的价格按键信号输入
input sys_clk,
input sys_rst_n,
output reg sign_mo, //价格显示控制信号
output reg sign_mx //计价显示控制信号
);
always@(posedge sys_clk or negedge sys_rst_n)
if(!sys_rst_n)begin
sign_mo<=0;
sign_mx<=0;end
else if(ok_in)
sign_mo<=1;
else if(cf_in)
sign_mx<=1;
endmodule
数码管驱动显示模块同样采用野火FPGA征途Pro代码,无需更改,直接引用即可。若大家有需求,我后续上传完整代码
2.9 buffer 报警模块
报警实现也很简单,当输入质量超出规定200克将蜂鸣器对应的端口置为有效的高电平,其他时刻无效
module buffer (
input sys_clk,
input sys_rst_n,
input [8:0]weigh,
output reg buff
);
always@(posedge sys_clk or negedge sys_rst_n)
if(!sys_rst_n)
buff<=1'b0;
else if(weigh>200)
buff<=1'b1;
else
buff<=1'b0;
endmodule
2.10 顶层设计
到这里所有的子模块就设计完毕了,接着只需要再创建个顶层文件进行连线即可
module weighing(
input wire sys_clk,
input wire sys_rst_n,
input wire cf,
input wire ok,
input wire qp,
//input wire [8:0]weigh,
output wire stcp,
output wire shcp,
output wire ds,
output wire oe,
output wire buff,
output wire i2c_scl , //输出至i2c设备的串行时钟信号scl
inout wire i2c_sda //输出至i2c设备的串行数据信号sda
);
wire [8:0]now_weigh;
wire [6:0]mx;
wire [9:0]moy;
wire sign_we;
wire sign_mo;
wire sign_mx;
wire [19:0]data;
wire cf1;
wire ok1;
wire qp1;
wire [8:0]gdata;
wire i2c_clk ; //i2c驱动时钟
wire i2c_start ; //i2c触发信号
wire [15:0] byte_addr ; //i2c字节地址
wire i2c_end ; //i2c一次读/写操作完成
wire [ 7:0] rd_data ; //i2c设备读取数据
wire [19:0] data1 ; //数码管待显示数据
wire rd_en ; //读使能信号
parameter DEVICE_ADDR = 7'h48 ; //i2c设备地址
parameter SYS_CLK_FREQ = 26'd50_000_000 ; //输入系统时钟频率
parameter SCL_FREQ = 18'd250_000 ; //i2c设备scl时钟频率
v_weigh v_weigh_inst(
.v_data(data1),
.sys_clk(sys_clk),
.sys_rst_n(sys_rst_n),
.g_data(gdata)
);
key_filter key_filter_inst1
(
.sys_clk(sys_clk) , //系统时钟50Mhz
.sys_rst_n(sys_rst_n) , //全局复位
.key_in(cf) , //按键输入信号
.key_flag(cf1) //key_flag为1时表示消抖后检测到按键被按下
//key_flag为0时表示没有检测到按键被按下
);
key_filter key_filter_inst2
(
.sys_clk(sys_clk) , //系统时钟50Mhz
.sys_rst_n(sys_rst_n) , //全局复位
.key_in(ok) , //按键输入信号
.key_flag(ok1) //key_flag为1时表示消抖后检测到按键被按下
//key_flag为0时表示没有检测到按键被按下
);
key_filter key_filter_inst3
(
.sys_clk(sys_clk) , //系统时钟50Mhz
.sys_rst_n(sys_rst_n) , //全局复位
.key_in(qp) , //按键输入信号
.key_flag(qp1) //key_flag为1时表示消抖后检测到按键被按下
//key_flag为0时表示没有检测到按键被按下
);
buffer buffer_inst(
.sys_clk(sys_clk),
.sys_rst_n(sys_rst_n),
.weigh(gdata),
.buff(buff)
);
weighing_pre weighing_pre_inst(
.weigh(gdata),//data1
.sys_clk(sys_clk),
.sys_rst_n(sys_rst_n),
.qp(qp1),
.now_weigh(now_weigh)
);
money money_inst(
.sys_clk(sys_clk),
.sys_rst_n(sys_rst_n),
.ok(ok1),
.now_weigh(now_weigh),
.mx(mx),
.moy(moy)
);
mcx mcx_inst(
.cf(cf1),
.sys_clk(sys_clk),
.sys_rst_n(sys_rst_n),
.mx(mx)
);
chose chose_inst(
.sys_clk(sys_clk),
.sys_rst_n(sys_rst_n),
//.sign_we(sign_we),
.sign_mo(sign_mo),
.sign_mx(sign_mx),
.mo(moy),
.mx(mx),
.now_weigh(now_weigh),
.data(data)
);
sign_give sign_give_inst(
.cf_in(cf1),
.ok_in(ok1),
//.qp_in(qp1),
.sys_clk(sys_clk),
.sys_rst_n(sys_rst_n),
.sign_mo(sign_mo),
//.sign_we(sign_we),
.sign_mx(sign_mx)
);
seg_595_dynamic seg_595_dynamic_inst
(
.sys_clk(sys_clk) , //系统时钟,频率50MHz
.sys_rst_n(sys_rst_n) , //复位信号,低有效
.data(data) , //数码管要显示的值
.point(6'b000000) , //小数点显示,高电平有效
.seg_en(1'b1) , //数码管使能信号,高电平有效
.sign(1'b0) , //符号位,高电平显示负号
.stcp(stcp) , //数据存储器时钟
.shcp(shcp) , //移位寄存器时钟
.ds(ds) , //串行数据输入
.oe(oe) //使能信号
);
//------------- pcf8591_adda_inst -------------
pcf8591_ad pcf8591_ad_inst
(
.sys_clk (i2c_clk ), //输入系统时钟,50MHz
.sys_rst_n (sys_rst_n ), //输入复位信号,低电平有效
.i2c_end (i2c_end ), //i2c设备一次读/写操作完成
.rd_data (rd_data ), //输出i2c设备读取数据
.rd_en (rd_en ), //输入i2c设备读使能信号
.i2c_start (i2c_start ), //输入i2c设备触发信号
.byte_addr (byte_addr ), //输入i2c设备字节地址
.po_data (data1 ) //数码管待显示数据
);
//------------- i2c_ctrl_inst -------------
i2c_ctrl
#(
.DEVICE_ADDR (DEVICE_ADDR ), //i2c设备器件地址
.SYS_CLK_FREQ (SYS_CLK_FREQ ), //i2c_ctrl模块系统时钟频率
.SCL_FREQ (SCL_FREQ ) //i2c的SCL时钟频率
)
i2c_ctrl_inst
(
.sys_clk (sys_clk ), //输入系统时钟,50MHz
.sys_rst_n (sys_rst_n ), //输入复位信号,低电平有效
.wr_en ( ), //输入写使能信号
.rd_en (rd_en ), //输入读使能信号
.i2c_start (i2c_start ), //输入i2c触发信号
.addr_num (1'b0 ), //输入i2c字节地址字节数
.byte_addr (byte_addr ), //输入i2c字节地址
.wr_data ( ), //输入i2c设备数据
.rd_data (rd_data ), //输出i2c设备读取数据
.i2c_end (i2c_end ), //i2c一次读/写操作完成
.i2c_clk (i2c_clk ), //i2c驱动时钟
.i2c_scl (i2c_scl ), //输出至i2c设备的串行时钟信号scl
.i2c_sda (i2c_sda ) //输出至i2c设备的串行数据信号sda
);
endmodule
三、烧录验证
设计侧重不在传感器模块,可以选择其他称重传感器替代文章传感器。因本设计的传感器信号较弱,所以在其输出后加了一个放大器。
放20g砝码相当于秤盘质量,此时数码管显示质量
按下去皮按键,去掉秤盘质量,显示置零
继续放两个20g砝码,显示40g
当按下计价键,此时数码管因为if语句优先级显示计价数据,假设1g3块钱,即按计价按键3次
按ok键,显示最终价格 3*40g=120
按复位键,显示秤盘+称重物质量之和
最后,个人能力有限,若有哪些描述不准确或有错误,希望大家多多包涵!
后续会不断发布一些相关设计。
标签:wire,FPGA,clk,电子秤,sys,verilog,rst,input,i2c From: https://blog.csdn.net/m0_64758206/article/details/144341164