FPGA实现IIC驱动环境光、距离传感器

简介

本次实验平台为野火征途mini开发板,用到的外设有按键、LED灯数码管、环境光(ALS)+距离(PS)传感器芯片。

AP3216C是一款环境光、距离传感器芯片,其接口为IIC接口,FPGA通过IIC接口可以配置工作模式、读取环境光、距离数据。

系统框图

系统模块连接如下:

FPGA实现IIC驱动环境光、距离传感器_第1张图片

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模块内部的状态机如下

FPGA实现IIC驱动环境光、距离传感器_第2张图片

根据数据手册,上电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驱动模块不详细介绍了。。。这里给出代码:

// -----------------------------------------------------------------------------
// 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

二进制转bcd码模块

verilog实现加3移位法-二进制转BCD码

数码管驱动模块

FPGA驱动74HC595实现数码管动态显示

你可能感兴趣的:(FPGA,fpga开发)