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

news2024/11/26 10:00:05

简介

本次实验平台为野火征途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驱动模块不详细介绍了。。。这里给出代码:

// -----------------------------------------------------------------------------
// 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实现数码管动态显示

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/760238.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

聊聊select for update到底加了什么锁

前言 最近在开发需求的时候&#xff0c;用到了select...for update。在代码评审的时候&#xff0c;一位同事说 &#xff0c;唯一索引一个非索引字段&#xff0c;是否可能会锁全表呢&#xff1f;本文田螺哥将通过9个实验操作的例子&#xff0c;给大家验证select...for update到…

回归预测 | MATLAB实现Attention-GRU多输入单输出回归预测(注意力机制融合门控循环单元,TPA-GRU)

回归预测 | MATLAB实现Attention-GRU多输入单输出回归预测----注意力机制融合门控循环单元&#xff0c;即TPA-GRU&#xff0c;时间注意力机制结合门控循环单元 目录 回归预测 | MATLAB实现Attention-GRU多输入单输出回归预测----注意力机制融合门控循环单元&#xff0c;即TPA-G…

一篇文章让你搞懂自定义类型---枚举与联合体

3.枚举 枚举顾名思义就是一一列举 把可能的取值一一列举 比如我们现实生活中 一周的星期一到星期日是有限的7天&#xff0c;可以一一列举 性别有&#xff1a;男、女、保密&#xff0c;也可以一一列举 月份有12个月&#xff0c;也可以一一列举 这里就可以使用枚举了 3.3.1 枚举…

JVM系列(5)——类加载过程

一、类的生命周期 加载&#xff08;Loading&#xff09;、验证&#xff08;Verification&#xff09;、准备&#xff08;Preparation&#xff09;、解析&#xff08;Resolution&#xff09;、初始化&#xff08;Initialization&#xff09;、使用&#xff08;Using&#xff09…

Lesson3-4:OpenCV图像处理---边缘检测

学习目标 了解Sobel算子&#xff0c;Scharr算子和拉普拉斯算子掌握canny边缘检测的原理及应用 1 原理 边缘检测是图像处理和计算机视觉中的基本问题&#xff0c;边缘检测的目的是标识数字图像中亮度变化明显的点。图像属性中的显著变化通常反映了属性的重要事件和变化。边缘的…

vuex使用/this.$store/分模块的vuex

vuex使用 this.$store.state mutation 简化写法 执行异步行为 actions 简化写法getters vuex分模块 访问子模块中的数据

Python程序编译为动态库pyd进行加密

1. 写一段Python代码 首先敲一段代码&#xff0c;这里在名为data.py的Python文件中写下一个求两数之和的简单函数&#xff0c;函数名为i_sum&#xff1b;需要注意一个问题&#xff0c;除了代码前两行常见内容&#xff0c;第3行添加 # cython: language_level3&#xff0c;以在…

【JMeter】四种参数化实现方式是什么?

1 参数化释义 什么是参数化&#xff1f;从字面上去理解的话&#xff0c;就是事先准备好数据&#xff08;广义上来说&#xff0c;可以是具体的数据值&#xff0c;也可以是数据生成规则&#xff09;&#xff0c;而非在脚本中写死&#xff0c;脚本执行时从准备好的数据中取值。 参…

【大模型】ChatGLM2-6B

参考 清华开源ChatGLM2-6B安装使用 手把手教程&#xff0c;轻松掌握 相关链接 代码&#xff1a;https://github.com/THUDM/ChatGLM2-6B 模型&#xff1a;https://huggingface.co/THUDM/chatglm2-6b、https://cloud.tsinghua.edu.cn/d/674208019e314311ab5c/?p%2Fchatglm2-6b&…

LiNux + 腾讯云 部署项目

1、介绍 Linux本地部署项目华为云简介腾讯云&#xff08;CVM&#xff09;远程部署CMS 2、Linux本地部署 2.1、引入 2.2、上传所需文件 文件里已经为大家准备了所需文件了&#xff1a; 在window上使用xftp工具&#xff0c;将linux版本的Jdk、tomcat、Mysql等软件上传至linux…

【电路原理学习笔记】第3章:欧姆定律:3.5 故障排查

第3章&#xff1a;欧姆定律 3.5 故障排查 故障排查是运用逻辑思维&#xff0c;结合对电路或习题运行的全面来纠正故障。故障排查的基本方法包括3个步骤&#xff1a;分析、规划和测量&#xff0c;将这三步方法称为APM。 3.5.1 分析 排查电路故障的第一步是分析故障的线索或症…

java项目之弹幕视频网站(ssm+mysql+jsp)

风定落花生&#xff0c;歌声逐流水&#xff0c;大家好我是风歌&#xff0c;混迹在java圈的辛苦码农。今天要和大家聊的是一款基于ssm的弹幕视频网站。技术交流和部署相关看文章末尾&#xff01; 目录 开发环境&#xff1a; 后端&#xff1a; 前端&#xff1a; 数据库&…

操作系统练习:创建内核模块,并加载和卸载模块

说明 本文记录如何创建和编译一个内核模块&#xff0c;以及加载和卸载内核模块。为《操作系统概念(第九版)》第二章&#xff0c;关于“Linux内核模块”的练习题。 创建内核模块 注&#xff1a;我这里是基于阿里云的轻量应用服务器&#xff08;即当前博客服务器&#xff09; 首…

JS混淆原理

JS混淆原理 •eval 加密 通过eval去执行函数通常和webpack打包拼接一起使用• 变量混淆 ​ 变量名混淆&#xff0c;十六进制替换&#xff0c;随机字符串替换• 属性加密 ​ 一套组合算法&#xff0c;将属性加密生成• 控制流平坦化 逻辑处理块统一加上前驱逻辑块&#xff0c…

最近写了一个Python知识分享网,开源了

大家好&#xff0c;我是锋哥&#xff01; 项目简介 肝了一周&#xff0c;Python知识分享网上线发布了。www.python222.com 虽然2很多&#xff0c;但是这个网站一点都不二&#xff0c;网站主要分享一些Python相关的技术知识&#xff0c;技术资源以及后面我的Python相关干货课程…

C# Modbus通信从入门到精通(9)——Modbus RTU(0x0F功能码)

1、0F(0x0F)写单个寄存器输出 使用该功能码能将一个寄存器的值写入到远程地址中。 2、发送报文格式 更详细的格式如下: 从站地址+功能码+线圈起始地址高字节+线圈起始地址低字节++线圈数量高字节+线圈数量低字节+字节计数+输出值最高字节+…+输出值最低字节+CRC,其中CRC是…

Linux学习之数组

数组可以存储同一类型的值&#xff0c;定义数组的常见方式是数组名(变量1 变量2 变量3......变量n)&#xff0c;使用小括号&#xff08;圆括号&#xff0c;()&#xff09;括起来&#xff0c;每个变量之间使用空格隔开。比如IPS数组可以存储多个ip变量&#xff0c;定义为IPS(192…

飞书ChatGPT机器人 – 打造智能问答助手实现无障碍交流

文章目录 前言环境列表1.飞书设置2.克隆feishu-chatgpt项目3.配置config.yaml文件4.运行feishu-chatgpt项目5.安装cpolar内网穿透6.固定公网地址7.机器人权限配置8.创建版本9.创建测试企业10. 机器人测试 前言 在飞书中创建chatGPT机器人并且对话&#xff0c;在下面操作步骤中…

设计模式再探-备忘录模式

目录 一、背景介绍二、思路&方案三、过程1.简介,定义2.类图3.符合面向对象的地方4.按照面向对象还可以优化的地方5.扩展-json转化、序列化 四、总结五、升华 一、背景介绍 最近在做一学期的语文课&#xff0c;每一节课结束的时候&#xff0c;需要将这节课上到哪儿了给记录…

SpringBoot使用JWT进行身份验证

JWT身份验证的流程 用户登录&#xff1a; 用户向服务器提供他们的用户名和密码。 服务器验证&#xff1a;服务器接收到请求&#xff0c;验证用户名和密码。 生成JWT&#xff1a;如果用户名和密码验证通过&#xff0c;服务器将创建一个 JWT。 JWT 包含了一些数据&#xff08;称…