• 御神楽的学习记录之基于FPGA的AHT10温湿度数据采集


    一、AHT10手册解析

    1.1 AHT10通信协议

    基于标准的I2C协议

    串行时钟SCL

    ​ SCL用于微处理器与AHT10之间的通讯同步。由于接口包含了完全静态逻辑,因而不存在最小SCL频率。

    串行数据SDA

    ​ SDA引脚用于传感器的数据输入和输出。当向传感器发送命令时,SDA在串行时钟(SCL)的上升沿有效,且当SCL为高电平时,SDA必须保持稳定。在SCL下降沿之后,SDA值可以被改变。
    在这里插入图片描述

    1.2 传感器启动

    启动传感器

    ​ 将传感器上电后,电压为所选择的VDD电源电压。需要注意的是,上电后传感器需要最多20毫秒时间(此时SCL为高电平)以达到空闲状态,即做好准备接收由主机(MCU)发送的命令。

    启动/停止时序

    ​ 每个传输序列都是以Start状态作为开始以Stop状态结束,如下图

    在这里插入图片描述

    Start状态

    ​ 当SCL为高电平时,SDA由高电平转换为低电平。开始状态是由主机控制的一种特殊的总线状态,指示从机传输开始。

    在这里插入图片描述

    Stop状态

    ​ 当SCL高电平时,SDA线上从低电平转换为高电平。停止状态是由主机控制的一种特殊的总线状态,指示从机传输结束。

    1.3 发送命令

    ​ 在启动传输后,随后传输I2C首字节包括7位的I2C设备地址0x38和一个SDA方向位x(读R:‘1’,写W:‘0’)。在第8个SCL时钟下降沿之后,通过拉低SDA引脚(ACK位),指示传感器数据接收正常。在发出初始化命令之后,主机MCU必须等待测量完成。

    基本命令集

    在这里插入图片描述

    状态位说明

    在这里插入图片描述

    1.4 传感器读取流程

    1.上电后要等待40ms,读取温湿度值之前,首先要看状态字的校准使能位Bit[3]是否位1(通过发送0x71可以获取一个字节的状态字),如果不为1,要发送0xE1命令(初始化),此命令参数有两个字节,第一个字节为0x08,第二个字节为0x00

    2.直接发送0xAC命令(触发测量),此命令参数有两个字节,第一个字节为0x33,第二个字节为0x00

    触发测量数据:
    在这里插入图片描述

    3.等待80ms待测量完成,忙状态Bit[7]为0,然后可以读取六个字节(发送0x71即可以读取)

    读取温湿度数据:

    在这里插入图片描述

    1.5数据转换

    相对湿度转换

    ​ 相对湿度计算公式:


    R H = ( S R H 2 20 ) ∗ 100 RH=(\frac{S_{RH}}{2^{20}})*100% RH=(220SRH)100
    温度转换

    ​ 温度转换公式:


    T ( ℃ ) = ( S T 2 20 ) ∗ 200 − 50 T(℃)=(\frac{S_{T}}{2^{20}})*200-50 T()=(220ST)20050

    二、项目架构

    2.1 项目整体架构

    ​		[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-xkVxYjUI-1659574913203)(C:\Users\KoTorILY\AppData\Roaming\Typora\typora-user-images\1659528514917.png)]

    ​ 项目总共分为4个小型模块

    I2C主机接口模块

    ​ 实现I2C协议,负责将对传感器的读写操作使用I2C协议发送给AHT10传感器

    AHT10配置模块

    ​ 负责传感器的配置、初始化、发测量使能等,以及将I2C接口模块的数据移位寄存送到串口发送控制模块

    串口发送控制模块

    ​ 负责将AHT配置模块的数据处理控制串口发送的数据的方式

    串口发送模块

    ​ 标准的串口发送模块,负责发送数据

    三、时序设计

    传感器配置模块状态机

    ​		[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-trrHRGCK-1659574913204)(C:\Users\KoTorILY\AppData\Roaming\Typora\typora-user-images\1659534052984.png)]

    四、模块解析

    4.1 传感器配置模块

    发送读写命令

     always @(*) begin
            if (state_c == INIT) begin
                case (cnt_byte)
                    0           : TX(1'b1,{`CMD_START|`CMD_WRITE},{`I2C_ADR,`WR_BIT});//发起始位、设备地址、写控制位
                    1           : TX(1'b1,`CMD_WRITE,{`AHT_INIT});//发初始化指令
                    2           : TX(1'b1,`CMD_WRITE,8'b0000_1000);//初始化指令数据
                    3           : TX(1'b1,{`CMD_WRITE|`CMD_STOP},8'b0000_0000);
                    default     : TX(1'b1,`CMD_WRITE,8'b0000_0000); 
                endcase                    
           end
           else if (state_c == MEASURE) begin
                case (cnt_byte)
                    0           : TX(1'b1,{`CMD_START|`CMD_WRITE},{`I2C_ADR,`WR_BIT});//发起始位、设备地址、写控制位
                    1           : TX(1'b1,`CMD_WRITE,{`AHT_MEASURE});//发触发测量指令
                    2           : TX(1'b1,`CMD_WRITE,8'b0011_0011);//触发测量指令数据
                    3           : TX(1'b1,{`CMD_WRITE|`CMD_STOP},8'b0000_0000);
                    default     : TX(1'b1,`CMD_WRITE,8'b0000_0000); 
                endcase                    
           end
           else if (state_c == READ) begin
                case (cnt_byte)
                    0           : TX(1'b1,{`CMD_START|`CMD_WRITE},{`I2C_ADR,`RD_BIT});//发起始位、设备地址、读控制位
                    READ_BYTE-1 : TX(1'b1,{`CMD_READ|`CMD_STOP},0);//最后一个字节读数据和发停止位
                    default     : TX(1'b1,{`CMD_READ},0);//读数据
                endcase
           end
           else begin
                 TX(1'b0,tx_cmd,tx_data);                
           end
        end
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30

    ​ 通过在不同的状态,如在发测量指令初始化状态,复用计数器,发送对应字节的数据给I2C接口模块

    计数器复用

    //cnt_byte  
        always @(posedge clk or negedge rst_n) begin 
            if (rst_n==0) begin
                cnt_byte <= 0; 
            end
            else if(add_cnt_byte) begin
                if(end_cnt_byte)
                    cnt_byte <= 0; 
                else
                    cnt_byte <= cnt_byte+1 ;
           end
        end
        assign add_cnt_byte = (state_c==MEASURE | state_c==READ|state_c==INIT)&done;
        assign end_cnt_byte = add_cnt_byte  && cnt_byte == ((state_c==MEASURE|state_c==INIT)?
                                                            (WRITE_BYTE-1):(READ_BYTE-1));
    
    //cnt_ms  
        always @(posedge clk or negedge rst_n) begin 
            if (rst_n==0) begin
                cnt_ms <= 0; 
            end
            else if(add_cnt_ms) begin
                if(end_cnt_ms)
                    cnt_ms <= 0; 
                else
                    cnt_ms <= cnt_ms+1 ;
           end
        end
        assign add_cnt_ms = (state_c== WAIT_40 | state_c == WAIT_80);
        assign end_cnt_ms = add_cnt_ms  && cnt_ms == ((state_c==WAIT_40)?
                                                            (TIME_40MS-1):(TIME_80MS-1));
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31

    ​ 根据不同的状态实现计数器的结束条件不同,达到复用目的。

    4.2 数据处理

    ​ 使用手册提供的公式计算即可

    always @(posedge clk or negedge rst_n) begin
          if (!rst_n) begin
             humi_data <= 20'b0;
             temp_data <= 20'b0;                    
          end
          else if (din_vld) begin
    
             humi_data <= (data_r[39:20]*100>>20);
              temp_data <= (data_r[19:0]*200>>20)-50;
                         
          end
          else begin
             humi_data <= humi_data; 
             temp_data <= temp_data;              
          end
       end
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    串口发送数据

    ​ 由于串口发送模块只能接收一字节的数据,过采用计数器计数字节数分字节发送。

    //字节计数器
       always @(posedge clk or negedge rst_n) begin
          if (!rst_n) begin
             cnt_byte <= 1'b0;
          end
          else if (end_cnt_byte) begin
             cnt_byte <= 1'b0;
          end
          else if (add_cnt_byte) begin
             cnt_byte <= cnt_byte + 1'b1;
          end
          else begin
             cnt_byte <= cnt_byte;
          end
          
       end
       assign add_cnt_byte = flag;//开始条件
       assign end_cnt_byte = add_cnt_byte && cnt_byte == BYTE_MAX -1;
    
       always @(posedge clk or negedge rst_n) begin
          if (!rst_n) begin
            data_tx_fifo <= 0;               
          end
          else if (flag) begin
             case (cnt_byte)
                0: data_tx_fifo <= 8'hce;
                1: data_tx_fifo <= 8'hc2;//温
                2: data_tx_fifo <= 8'hb6;
                3: data_tx_fifo <= 8'hc8;//度
                4: data_tx_fifo <= 8'h3a;//:
                5: data_tx_fifo <= (temp_data /10 %10)+48;
                6: data_tx_fifo <= (temp_data  %10)+48;
                7: data_tx_fifo <= 8'ha1;
                8: data_tx_fifo <= 8'he6;//℃
    
                9: data_tx_fifo <= 8'hca;
                10: data_tx_fifo <= 8'haa;//湿
               11: data_tx_fifo <= 8'hb6;
               12: data_tx_fifo <= 8'hc8;//度
               13: data_tx_fifo <= 8'h3a;//:
               14: data_tx_fifo <= (humi_data /10%10)+48;
               15: data_tx_fifo <= (humi_data %10)+48;
               16: data_tx_fifo <= 8'h25;//%
                default:data_tx_fifo <= 8'h0 ;
             endcase              
          end  
       end
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47

    ​ 数据发送缓存FIFO

    	assign rdreq = busy&&~empty;
        assign wrreq = ~full && flag_r;
      
       fifo	fifo_inst (
    	.clock         ( clk ),
    	.data ( data_tx_fifo ),
    	.rdreq       ( rdreq ),
    	.wrreq       ( wrreq ),
    	.empty       ( empty ),
    	.full         ( full ),
    	.q          ( data_tx),
    	.usedw   ( usedw_sig )
    	);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    将处理好的每个字节数据存入FIFO,通过串口的发送完成信号来控制读取FIFO数据到串口发送模块中去

    五、模块测试

    项目整体仿真

    传感器初始化

    在这里插入图片描述

    发送测量命令

    在这里插入图片描述

    发送读命令
    在这里插入图片描述

    SignalTap

    串口发送数据控制波形

    在这里插入图片描述

    读状态I2C协议波形及数据

    在这里插入图片描述

    串口发送效果

    在这里插入图片描述

  • 相关阅读:
    问题:先后键入字符串和字符,结果发生冲突
    物联网智慧养老平台解析
    (仿牛客论坛项目)01 - 开发社区首页
    Monaco Editor教程(十七):代码信息指示器CodeLens配置详解
    HTML VUE
    浮动哈哈哈
    任务四 机器学习库Scikit-learn
    【蓝桥杯选拔赛真题62】Scratch判断小球 少儿编程scratch图形化编程 蓝桥杯选拔赛真题解析
    vue computed计算属性
    oracle 临时表
  • 原文地址:https://blog.csdn.net/YuKaguraNe/article/details/126152722