本次实验平台为野火征途mini开发板,用到的外设有按键、LED灯数码管、环境光(ALS)+距离(PS)传感器芯片。
AP3216C是一款环境光、距离传感器芯片,其接口为IIC接口,FPGA通过IIC接口可以配置工作模式、读取环境光、距离数据。
系统模块连接如下:
key_filter
模块实现按键消抖功能,mode_reg
是1bit寄存器,检测到按键脉冲则翻转,mode会通过led显示,ALS_PS_driver
模块负责通过IIC总线驱动AP3216C芯片,其内部有一个状态机和一个IIC驱动模块。
当mode为0时,读取环境光的16bit的二进制数据,通过一个16bit的二进制转bcd码模块将二进制数据转化为bcd码,最后通过数码管驱动模块显示在开发板的数码管上。当mode为1时,读取、显示的则是距离数据。
接下来主要介绍ALS_PS_driver
模块,其他模块就不介绍了
ALS_PS_driver
模块内部的状态机如下
根据数据手册,上电200ms后,进入CONFIG状态,配置芯片工作模式为环境光+距离传感器都激活。
配置好工作模式后,该传感器芯片会将模拟信号转化为数字信号,供我们用IIC接口读取,这个过程需要一定时间,根据数据手册,距离数据转化需要12.5ms,环境光数据转化需要100ms,总共需要112.5ms,每次读取间隔大于112.5ms即可,同时为了防止数据变化太快不方便观察,本次实验设定读取间隔为200ms。
进入DELAY延时状态,当mode为0时,进入环境光数据读取循环,每200ms读取一次环境光数据,当mode为1时,进入距离数据读取循环,每200ms读取一次距离数据。
代码如下:
module ALS_PS_driver(
input clk,
input rst_n,
input mode,
output reg [15:0] ALS_data,
output reg [9:0] PS_data,
// iic
output scl,
inout sda
);
//------------signals--------------
// 状态机信号
localparam INIT = 4'h0, // 上电延时200ms
CONFIG = 4'h1, // 模式配置
DELAY = 4'h2, // 数据转化等待200ms
IIC_READ_ALS_L = 4'h3, // 读取ALS低8位
IIC_WAIT_1 = 4'h4, // 等待ALS低8位读取完成
IIC_READ_ALS_H = 4'h5, // 读取ALS高8位
IIC_READ_PS_L = 4'h6, // 读取PS低4位
IIC_WAIT_2 = 4'h7, // 等待PS低4位读取完成
IIC_READ_PS_H = 4'h8; // 读取PS高6位
reg [3:0] state;
// 200ms延时计数器
reg [23:0] cnt;
wire cnt_end = (cnt == 24'd10_000_000);
// iic读写信号
reg rd_req;
reg wr_req;
reg [7:0] addr;
reg [7:0] wr_data;
wire rd_valid;
wire [7:0] rd_data;
//------------function-------------
// 200ms计数器
always @(posedge clk, negedge rst_n) begin
if(!rst_n)
cnt <= 0;
else if(state == INIT || state == DELAY)
cnt <= cnt_end ? 24'd0 : (cnt + 24'd1);
end
// 状态机
always @(posedge clk, negedge rst_n) begin
if(!rst_n)
state <= INIT;
else begin
case(state)
INIT : state <= cnt_end ? CONFIG : INIT;
CONFIG : state <= DELAY;
DELAY : state <= cnt_end ? (mode ? IIC_READ_PS_L : IIC_READ_ALS_L) : DELAY;
IIC_READ_ALS_L: state <= IIC_WAIT_1;
IIC_WAIT_1 : state <= rd_valid ? IIC_READ_ALS_H : IIC_WAIT_1;
IIC_READ_ALS_H: state <= DELAY;
IIC_READ_PS_L : state <= IIC_WAIT_2;
IIC_WAIT_2 : state <= rd_valid ? IIC_READ_PS_H : IIC_WAIT_2;
IIC_READ_PS_H : state <= DELAY;
default : state <= INIT;
endcase
end
end
// iic读写信号
always @(*) begin
case(state)
CONFIG: begin
rd_req = 1'b0;
wr_req = 1'b1;
addr = 8'h00;
wr_data = 8'h03;
end
IIC_READ_ALS_L: begin
rd_req = 1'b1;
wr_req = 1'b0;
addr = 8'h0c;
wr_data = 8'h00;
end
IIC_READ_ALS_H: begin
rd_req = 1'b1;
wr_req = 1'b0;
addr = 8'h0d;
wr_data = 8'h00;
end
IIC_READ_PS_L: begin
rd_req = 1'b1;
wr_req = 1'b0;
addr = 8'h0e;
wr_data = 8'h00;
end
IIC_READ_PS_H: begin
rd_req = 1'b1;
wr_req = 1'b0;
addr = 8'h0f;
wr_data = 8'h00;
end
default : begin
rd_req = 1'b0;
wr_req = 1'b0;
addr = 8'h00;
wr_data = 8'h00;
end
endcase
end
// 读取ALS数据到寄存器
always @(posedge clk, negedge rst_n) begin
if(!rst_n)
ALS_data <= 0;
else if(~mode) begin
if(rd_valid) begin
if(state == IIC_WAIT_1)
ALS_data[7:0] <= rd_data;
else
ALS_data[15:8] <= rd_data;
end
end
end
// 读取PS数据到寄存器
always @(posedge clk, negedge rst_n) begin
if(!rst_n)
PS_data <= 0;
else if(mode) begin
if(rd_valid) begin
if(state == IIC_WAIT_2)
PS_data[3:0] <= rd_data[3:0];
else
PS_data[9:4] <= rd_data[5:0];
end
end
end
// iic驱动模块
iic_driver #(
.ADDR_WIDTH (1),
.DEV_ADDR (7'b0011110)
) inst_iic_driver (
.clk (clk),
.rst_n (rst_n),
.rd_req (rd_req),
.wr_req (wr_req),
.addr ({8'h00, addr}),
.wr_data (wr_data),
.rd_valid (rd_valid),
.rd_data (rd_data),
.scl (scl),
.sda (sda)
);
endmodule
IIC驱动模块不详细介绍了。。。这里给出代码:
// -----------------------------------------------------------------------------
// iic驱动,支持400khz,支持地址位宽1字节、2字节,支持单字节读写
// -----------------------------------------------------------------------------
module iic_driver
#(
// 寄存器地址宽度
parameter ADDR_WIDTH = 2,
// 野火开发板EEPROM设备地址为0x53,1010011,这里作为默认地址
parameter DEV_ADDR = 7'b1010011
)
(
input clk,
input rst_n,
// host side
input rd_req,
input wr_req,
input [15:0] addr,
input [7:0] wr_data,
output reg rd_valid,
output reg [7:0] rd_data,
// iic side
output reg scl,
inout sda
);
//-----------------------------------信号声明-----------------------------------
// 由于写法问题,SCL频率默认400k,不支持100k和1M
localparam SCL_FREQ = 400_000;
wire [7:0] DEV_ADDR_W = {DEV_ADDR, 1'b0};// 7bit设备地址 + 1bit写命令(低电平)
wire [7:0] DEV_ADDR_R = {DEV_ADDR, 1'b1};// 7bit设备地址 + 1bit读命令(高电平)
// 状态机,一共16个状态
reg [3:0] state, next;
localparam IDLE = 0, // 空闲
START1 = 1, // 起始位1
DEV_W = 2, // 7bit设备地址 + 1bit写命令(低电平)
ACK1 = 3, // 设备地址应答
ADDR_H = 4, // 地址高字节
ACK2 = 5, // 地址高字节应答
ADDR_L = 6, // 地址低字节
ACK3 = 7, // 地址低字节应答
WR_DATA = 8, // 写数据
ACK4 = 9, // 写数据应答
START2 = 10, // 起始位2
DEV_R = 11, // 7bit设备地址 + 1bit读命令(高电平)
ACK5 = 12, // 设备地址应答
RD_DATA = 13, // 读数据
NO_ACK = 14, // 无应答
STOP = 15; // 停止位
// 读写状态寄存,0为写,1为读
reg is_read;
// wr_data寄存
reg [7:0] wr_data_r;
// addr寄存
reg [7:0] addr_h, addr_l;
// 读数据寄存器
reg [7:0] rd_data_r;
// 应答信号寄存
reg ack_r;
// scl计数器,400khz则为125时钟周期,但125不能被4整除,所以选择128,产生的scl频率约为390khz
reg [6:0] scl_cnt;
// bit计数器
reg [2:0] bit_cnt;
// 内部sda
reg sda_r;
// sda三态门输出使能
wire sda_oe;
// sda三态门
assign sda = sda_oe ? sda_r : 1'bz;
// scl计数器满,127,即7'b1111111
wire scl_cnt_end = &scl_cnt;
// 1字节结束,当scl_cnt == 7'b1111111 且 bit_cnt == 3'b111时,表示1byte结束
wire byte_end = scl_cnt_end & (&bit_cnt);
//---------------------------------输入信号寄存---------------------------------
// 地址寄存
always @(posedge clk, negedge rst_n) begin
if(!rst_n)
{addr_h, addr_l} <= 0;
else if(state == IDLE && (rd_req | wr_req))
{addr_h, addr_l} <= addr;
end
// 写数据寄存
always @(posedge clk, negedge rst_n) begin
if(!rst_n)
wr_data_r <= 0;
else if(state == IDLE & wr_req)
wr_data_r <= wr_data;
end
//-----------------------------------------------------------------------------
// 读状态寄存,读优先,不在读状态即为写状态
always @(posedge clk, negedge rst_n) begin
if(!rst_n)
is_read <= 1'b0;
else if(state == IDLE)
is_read <= rd_req;
end
// 状态机
always @(posedge clk, negedge rst_n) begin
if(!rst_n)
state <= IDLE;
else
state <= next;
end
always @(*) begin
case(state)
IDLE : next = (rd_req | wr_req) ? START1 : IDLE;
START1 : next = scl_cnt_end ? DEV_W : START1;
DEV_W : next = byte_end ? ACK1 : DEV_W;
ACK1 : next = scl_cnt_end ? (~ack_r ? (ADDR_WIDTH == 2 ? ADDR_H : ADDR_L) : IDLE) : ACK1;
ADDR_H : next = byte_end ? ACK2 : ADDR_H;
ACK2 : next = scl_cnt_end ? (~ack_r ? ADDR_L : IDLE) : ACK2;
ADDR_L : next = byte_end ? ACK3 : ADDR_L;
ACK3 : next = scl_cnt_end ? (~ack_r ? (is_read ? START2 : WR_DATA) : IDLE) : ACK3;
WR_DATA: next = byte_end ? ACK4 : WR_DATA;
ACK4 : next = scl_cnt_end ? (~ack_r ? STOP : IDLE) : ACK4;
START2 : next = scl_cnt_end ? DEV_R : START2;
DEV_R : next = byte_end ? ACK5 : DEV_R;
ACK5 : next = scl_cnt_end ? (~ack_r ? RD_DATA : IDLE) : ACK5;
RD_DATA: next = byte_end ? NO_ACK : RD_DATA;
NO_ACK : next = scl_cnt_end ? STOP : NO_ACK;
STOP : next = scl_cnt_end ? IDLE : STOP;
default: next = IDLE;
endcase
end
// scl计数器
always @(posedge clk, negedge rst_n) begin
if(!rst_n)
scl_cnt <= 0;
else if(state != IDLE)
scl_cnt <= scl_cnt + 7'd1;
end
// bit计数器
always @(posedge clk, negedge rst_n) begin
if(!rst_n)
bit_cnt <= 0;
else if(state == DEV_W || state == ADDR_H || state == ADDR_L || state == WR_DATA || state == DEV_R || state == RD_DATA) begin
if(scl_cnt_end)
bit_cnt <= bit_cnt + 3'd1;
end
end
// scl 每个周期持续128系统时钟周期,scl_cnt[6:5]变化规律为00-01-10-11,所以可以用于调整scl电平,保证sda在scl低电平中间进行跳转
always @(*) begin
case(state)
IDLE : scl = 1'b1;
// 与非 1110
// ___
// |_
START1 : scl = ~(scl_cnt[6] & scl_cnt[5]);
// 或 0111
// ___
// _|
STOP : scl = scl_cnt[6] | scl_cnt[5];
// 异或 0110
// __
// _| |_
default: scl = scl_cnt[6] ^ scl_cnt[5];
endcase
end
// sda_oe 在应答状态和读数据状态,允许sda输入
assign sda_oe = ~((state == ACK1) || (state == ACK2) || (state == ACK3) || (state == ACK4) || (state == ACK5) || (state == RD_DATA));
// sda_r
always @(*) begin
case(state)
START1, START2: sda_r = ~scl_cnt[6]; // 下降沿
DEV_W : sda_r = DEV_ADDR_W[~bit_cnt]; // 7bit设备地址 + 1bit写命令
ADDR_H : sda_r = addr_h[~bit_cnt]; // 地址高字节
ADDR_L : sda_r = addr_l[~bit_cnt]; // 地址低字节
WR_DATA : sda_r = wr_data_r[~bit_cnt]; // 写数据
DEV_R : sda_r = DEV_ADDR_R[~bit_cnt]; // 7bit设备地址 + 1bit读命令
STOP : sda_r = scl_cnt[6]; // 上升沿
default : sda_r = 1'b1;
endcase
end
// 采样应答信号
always @(posedge clk, negedge rst_n) begin
if(!rst_n)
ack_r <= 1'b1;
else if((state == ACK1) || (state == ACK2) || (state == ACK3) || (state == ACK4) || (state == ACK5)) begin
if(scl_cnt == 7'b0111111) // 在scl高电平中间进行采样
ack_r <= sda;
end else
ack_r <= 1'b1;
end
// 读数据
always @(posedge clk, negedge rst_n) begin
if(!rst_n)
rd_data_r <= 0;
else if(state == RD_DATA && scl_cnt == 7'b0111111)// 在scl高电平中间进行采样
rd_data_r <= {rd_data_r[6:0], sda};
end
// 输出读数据
always @(posedge clk, negedge rst_n) begin
if(!rst_n) begin
rd_valid <= 1'b0;
rd_data <= 8'h00;
end else if(is_read && state == STOP && scl_cnt_end) begin
rd_valid <= 1'b1;
rd_data <= rd_data_r;
end else begin
rd_valid <= 1'b0;
rd_data <= 8'h00;
end
end
endmodule
verilog实现加3移位法-二进制转BCD码
FPGA驱动74HC595实现数码管动态显示