<meter id="pryje"><nav id="pryje"><delect id="pryje"></delect></nav></meter>
          <label id="pryje"></label>

          新聞中心

          EEPW首頁 > 嵌入式系統 > 設計應用 > 基于接近式傳感器的智能接近系統設計

          基于接近式傳感器的智能接近系統設計

          作者:時間:2023-12-14來源:電子森林收藏

          實驗任務

          • 任務:智能手機通話,手機靠近耳朵后關閉屏顯,基于核心板 和 底板 完成智能接近系統設計并觀察調試結果
          • 要求:驅動底板上的接近式傳感器APDS-9901獲得接近數據,控制核心板上LED按能量條方式點亮
          • 解析:通過編程驅動接近式傳感器APDS-9901,獲取接近距離信息,然后根據距離信息編碼控制8個LED燈按能量條方式點亮。

          實驗目的

          本節實驗主要學習I2C總線工作原理、協議及相關知識,掌握驅動I2C設備的原理及方法,了解輸入輸出型端口的模型及控制實現,最終實現智能接近系統的總體設計。

          本文引用地址:http://www.ex-cimer.com/article/202312/453885.htm
          • 熟悉I2C總線工作原理及通信協議
          • 完成I2C接口接近式傳感器的驅動
          • 完成智能接近系統設計實現

          設計框圖

          根據前面的實驗解析我們可以得知,該設計可以拆分成兩個功能模塊實現,

          • APDS9901Driver:接近式傳感器APDS-9901芯片I2C總線通信驅動模塊。
          • Decoder:將距離信息轉換成能量條數據。

          Top-Down層次設計

          模塊結構設計

          實驗原理

          I2C總線介紹

          I2C總線是由Philips公司開發的一種簡單、雙向二線制同步串行總線。它只需要兩根線即可在連接于總線上的器件之間傳送信息。

          I2C總線連接

          主器件用于啟動總線傳送數據,并產生時鐘以開放傳送的器件,此時任何被尋址的器件均被認為是從器件.在總線上主和從、發和收的關系不是恒定的,而取決于此時數據傳送方向。如果主機要發送數據給從器件,則主機首先尋址從器件,然后主動發送數據至從器件,最后由主機終止數據傳送;如果主機要接收從器件的數據,首先由主器件尋址從器件.然后主機接收從器件發送的數據,最后由主機終止接收過程。在這種情況下.主機負責產生定時時鐘和終止數據傳送。

          I2C通信時序

          字節格式

          發送到SDA 線上的每個字節必須為8 位,每次傳輸可以發送的字節數量不受限制。每個字節后必須跟一個響應位。首先傳輸的是數據的最高位(MSB),如果從機要完成一些其他功能后(例如一個內部中斷服務程序)才能接收或發送下一個完整的數據字節,可以使時鐘線SCL 保持低電平,迫使主機進入等待狀態,當從機準備好接收下一個數據字節并釋放時鐘線SCL 后數據傳輸繼續。

          I2C字節格式

          啟動和停止

          在時鐘線SCL保持高電平期間,數據線SDA上的電平被拉低(即負跳變),定義為I2C總線總線的啟動信號,它標志著一次數據傳輸的開始。啟動信號是一種電平跳變時序信號,而不是一個電平信號。啟動信號是由主控器主動建立的,在建立該信號之前I2C總線必須處于空閑狀態。

          在時鐘線SCL保持高電平期間,數據線SDA被釋放,使得SDA返回高電平(即正跳變),稱為I2C總線的停止信號,它標志著一次數據傳輸的終止。停止信號也是一種電平跳變時序信號,而不是一個電平信號,停止信號也是由主控器主動建立的,建立該信號之后,I2C總線將返回空閑狀態。

          啟動和停止格式

          應答響應

          數據傳輸必須帶響應,相關的響應時鐘脈沖由主機產生。在響應的時鐘脈沖期間,發送器釋放SDA 線(上拉電阻拉高),接收器必須將SDA 線拉低,使它在這個時鐘脈沖的高電平期間保持穩定的低電平,這種情況下是應答,如果在這個時鐘脈沖的高電平期間SDA線沒有被拉低則表示沒有應答。通常被尋址的接收器在接收到的每個字節后,必須產生一個應答。當從機接收器不應答時,主機產生一個停止或重復起始條件。

          應答響應格式

          通信速率

          常見的I2C總線依傳輸速率的不同而有不同的模式:標準模式(100 Kbit/s)、低速模式(10 Kbit/s),但時鐘頻率可被允許下降至零,這代表可以暫停通信。而新一代的I2C總線可以和更多的節點(支持10比特長度的地址空間)以更快的速率通信:快速模式(400 Kbit/s)、高速模式(3.4 Mbit/s)。

          APDS-9901模塊連接

          底板上的接近光傳感器APDS-9901模塊電路圖如下(上拉電阻未顯示):

          APDS-9901模塊電路

          上圖為接近光傳感器APDS-9901模塊電路,與FPGA硬件接口有I2C總線(SCL、SDA)和中斷信號INT,APDS-9901是博通公司的集成環境光ALS、紅外光IR和接近距離傳感器,具有體積小、低功耗等優點,被大量應用于手機、筆記本、相機、液晶顯示器等電子產品上,環境光ALS可以根據外部環境調節設備屏幕顯示亮度,接近距離傳感器可以根據應用場景實現產品對應應用,例如接聽電話時控制手機關閉顯示等,接口采用I2C總線能夠支持400KHz的I2C快速模式。

          APDS-9901內部框圖

          雙向端口設計

          可綜合Verilog模塊設計中必須有端口存在,端口有輸入input,輸出output,雙向inout,對于輸入和輸出型端口我們很好理解,我們來了解一下雙向端口信號的處理。

          在芯片中為了管腳復用,很多管腳都是雙向的,既可以輸入也可以輸出。在Verilog中即為inout型端口。Inout端口的實現是使用三態門,三態門的第三個狀態是高阻態Z。在實際電路中高阻態意味著響應的管腳懸空、斷開。

          FPGA引腳輸入輸出模型

          當inout用作輸出時,就像平常一樣。當inout用作輸入時,需要設為高阻態,這樣其電平就可以由外部輸入信號決定了(這是高阻態的特性)。

          雙向端口應用案例:

          module bid
          (
          input           out_en,
          input           a,
          inout           b,
          output          c
          );  
          assign  b = out_en? a : 1'bz;
          assign  c = b; 
          endmodule
          APDS-9901驅動設計

          通過前面的了解,我們對于整個I2C總線的驅動原理有了一定的了解,接下來我們根據APDS-9901的芯片手冊了解其驅動方法及參數要點。

          APDS-9901時序

          APDS-9901時序參數

          通過APDS-9901時序參數了解,APDS-9901支持I2C通信400KHz快速模式同時兼容100KHz的標準模式,還有兩種模式下時序中的各種時間參數,本例中我們就采用標準模式完成驅動設計。

          首先我們分頻得到400KHz的時鐘,整個設計都基于該時鐘完成,程序實現如下:

          //使用計數器分頻產生400KHz時鐘信號
          clk_400khzreg                 clk_400khz;
          reg     [9:0]       cnt_400khz;
          always@(posedge clk or negedge rst_n) begin
              if(!rst_n) begin
                  cnt_400khz <= 10'd0; 
                  clk_400khz <= 1'b0;
              end else if(cnt_400khz >= CNT_NUM-1) begin
                  cnt_400khz <= 10'd0; 
                  clk_400khz <= ~clk_400khz;
              end else begin
                  cnt_400khz <= cnt_400khz + 1'b1;
              end
              end

          I2C時序可以分解成基本單元(啟動、停止、發送、接收、發應答、讀應答),整個I2C通信都是由這些單元按照不同的順序組合,我們設計一個狀態機,將這些基本單元做成狀態,控制狀態機的跳轉就能實現I2C通信時序。主機每次發送數據都要接收判斷從機的響應,每次接收數據也要向從機發送響應,所以發送單元和讀應答單元可以合并,接收單元和寫應答單元可以合并。

          啟動時序狀態設計程序實現如下:

          START:begin //I2C通信時序中的起始START
                  if(cnt_start >= 3'd5) cnt_start <= 1'b0;    //對START中的子狀態執行控制cnt_start
                  else cnt_start <= cnt_start + 1'b1;
                  case(cnt_start)
                      3'd0:   begin sda <= 1'b1; 
                      scl <= 1'b1; 
                      end //將SCL和SDA拉高,保持4.7us以上
                      3'd1:   begin sda <= 1'b1; 
                      scl <= 1'b1; 
                      end //每個周期2.5us,需要兩個周期
                      3'd2:   begin sda <= 1'b0; 
                      end  //SDA拉低到SCL拉低,保持4.0us以上
                      3'd3:   begin sda <= 1'b0; 
                      end  //clk_400khz每個周期2.5us,需要兩個周期
                      3'd4:   begin scl <= 1'b0; 
                      end  //SCL拉低,保持4.7us以上
                      3'd5:   begin scl <= 1'b0; 
                      state <= state_back; 
                      end //每個周期2.5us,兩個周期
                      default: state <= IDLE; //如果程序失控,進入IDLE自復位狀態
                  endcase
              end

          發送單元和讀應答單元合并,時序狀態設計程序實現如下:

          WRITE:begin //I2C通信時序中的寫操作WRITE和相應判斷操作ACK
                  if(cnt <= 3'd6) begin   //共需要發送8bit的數據,這里控制循環的次數
                      if(cnt_write >= 3'd3) begin cnt_write <= 1'b0; 
                      cnt <= cnt + 1'b1; 
                      end
                      else begin cnt_write <= cnt_write + 1'b1; 
                      cnt <= cnt; 
                      end
                  end else begin
                      if(cnt_write >= 3'd7) begin cnt_write <= 1'b0; 
                      cnt <= 1'b0; 
                      end //復位變量
                      else begin cnt_write <= cnt_write + 1'b1; 
                      cnt <= cnt; 
                      end
                  end
                  case(cnt_write)
                      //按照I2C的時序傳輸數據
                      3'd0:   begin scl <= 1'b0; 
                      sda <= data_wr[7-cnt]; 
                      end   //SCL拉低,SDA輸出
                      3'd1:   begin scl <= 1'b1; 
                      end  //SCL拉高,保持4.0us以上
                      3'd2:   begin scl <= 1'b1; 
                      end  //clk_400khz每個周期2.5us,需要兩個周期
                      3'd3:   begin scl <= 1'b0; 
                      end  //SCL拉低,準備發送下1bit的數據
                      //獲取從設備的響應信號并判斷
                      3'd4:   begin sda <= 1'bz; 
                      end  //釋放SDA線,準備接收從設備的響應信號
                      3'd5:   begin scl <= 1'b1; 
                      end  //SCL拉高,保持4.0us以上
                      3'd6:   begin ack_flag <= i2c_sda; 
                      end  //獲取從設備的響應信號
                      3'd7:   begin scl <= 1'b0; 
                      if(ack_flag)state <= state; 
                      else state <= state_back; 
                      end //SCL拉低,如果不應答循環寫
                      default: state <= IDLE; //如果程序失控,進入IDLE自復位狀態
                  endcase
              end

          接收單元和寫應答單元合并,時序狀態設計程序實現如下:

          READ:begin  //I2C通信時序中的讀操作READ和返回ACK的操作
                  if(cnt <= 3'd6) begin   //共需要接收8bit的數據,這里控制循環的次數
                      if(cnt_read >= 3'd3) begin cnt_read <= 1'b0; 
                      cnt <= cnt + 1'b1; 
                      end
                      else begin cnt_read <= cnt_read + 1'b1; 
                      cnt <= cnt; 
                      end
                  end else begin
                      if(cnt_read >= 3'd7) begin cnt_read <= 1'b0; 
                      cnt <= 1'b0; 
                      end   //復位變量值
                      else begin cnt_read <= cnt_read + 1'b1; 
                      cnt <= cnt; 
                      end
                  end
                  case(cnt_read)
                      //按照I2C的時序接收數據
                      3'd0:   begin scl <= 1'b0; 
                      sda <= 1'bz; 
                      end //SCL拉低,釋放SDA線
                      3'd1:   begin scl <= 1'b1; 
                      end  //SCL拉高,保持4.0us以上
                      3'd2:   begin data_r[7-cnt] <= i2c_sda; 
                      end //讀取從設備返回的數據
                      3'd3:   begin scl <= 1'b0; 
                      end  //SCL拉低,準備接收下1bit的數據
                      //向從設備發送響應信號
                      3'd4:   begin sda <= ack; 
                      end   //發送響應信號,將前面接收的數據鎖存
                      3'd5:   begin scl <= 1'b1; 
                      end  //SCL拉高,保持4.0us以上
                      3'd6:   begin scl <= 1'b1; 
                      end  //SCL拉高,保持4.0us以上
                      3'd7:   begin scl <= 1'b0; 
                      state <= state_back; 
                      end //SCL拉低 
                      default: state <= IDLE; //如果程序失控,進入IDLE自復位狀態
                  endcase
              end

          停止時序狀態設計程序實現如下:

          STOP:begin  //I2C通信時序中的結束STOP
                  if(cnt_stop >= 3'd5) cnt_stop <= 1'b0;  //對STOP中的子狀態執行控制cnt_stop
                  else cnt_stop <= cnt_stop + 1'b1;
                  case(cnt_stop)
                      3'd0:   begin sda <= 1'b0; 
                      end  //SDA拉低,準備STOP
                      3'd1:   begin sda <= 1'b0; 
                      end  //SDA拉低,準備STOP
                      3'd2:   begin scl <= 1'b1; 
                      end  //SCL提前SDA拉高4.0us
                      3'd3:   begin scl <= 1'b1; 
                      end  //SCL提前SDA拉高4.0us
                      3'd4:   begin sda <= 1'b1; 
                      end  //SDA拉高
                      3'd5:   begin sda <= 1'b1; 
                      state <= state_back; 
                      end //完成STOP操作
                      default: state <= IDLE; //如果程序失控,進入IDLE自復位狀態
                  endcase
              end

          基本單元都有了,接下來我們需要了解APDS-9901驅動的流程,手冊上看到APDS-9901芯片有很多寄存器,有的配置工作模式,有的配置功能使能,有的返回結果數據,這個需要大家自己查看芯片手冊,這里不作講解。

          APDS-9901寄存器

          手冊給用戶提供了基本功能的C實例代碼,流程如下:

          WriteRegData (0, 0); //Disable and Powerdown 
          WriteRegData (1, 0xff); // 2.7 ms – minimum ALS integration time
          WriteRegData (2, 0xff); // 2.7 ms – minimum Prox integration time
          WriteRegData (3, 0xff); // 2.7 ms – minimum Wait time
          WriteRegData (0xe, 1);  // Minimum prox pulse count
          WriteRegData (0xf, 0x20); // CH1 Diode
          WriteRegData (0,0x0f); //Enable WEN PEN AEN PON
          Wait(12);  //Wait for 12 ms 
          CH0_data = Read_Word(0x14); 
          CH1_data = Read_Word(0x16); 
          Prox_data = Read_Word(0x18);  
          WriteRegData(uint8 reg, uint8 data) 
          { 
              m_I2CBus.WriteI2C(0x39, 0x80 | reg, 1, &data); 
          } 
              uint16 Read_Word(uint8 reg)
          { 
              uint8 barr[2]; 
              m_I2CBus.ReadI2C(0x39, 0xA0 | reg, 2, ref barr); 
              return (uint16)(barr[0] + 256 * barr[1]); 
          }

          根據手冊提供的軟件操作流程,我們首先有7次向寄存器寫入數據的操作,按照時序

          I2C寫操作

          向regaddr地址寄存器中寫入數據regdata,程序實現如下

          4'd0:   begin state <= START; 
          end   //I2C通信時序中的START
          4'd1:   begin data_wr <= dev_addr<<1; 
          state <= WRITE; 
          end  //設備地址
          4'd2:   begin data_wr <= reg_addr; 
          state <= WRITE; end  //寄存器地址
          4'd3:   begin data_wr <= reg_data; 
          state <= WRITE; 
          end  //寫入數據
          4'd4:   begin state <= STOP; 
          end    //I2C通信時序中的STOP

          7次向寄存器寫入數據的操作需要7段上面的代碼,羅列起來程序不易讀,干脆我們將1次寫操作做成狀態機的一個狀態,這樣7次向寄存器寫入數據的操作只需要在這個狀態上循環執行7次就好了,單詞寫操作狀態程序實現如下:

          MODE1:begin //單次寫操作
                  if(cnt_mode1 >= 4'd5) cnt_mode1 <= 1'b0;    //對START中的子狀態執行控制cnt_start
                  else cnt_mode1 <= cnt_mode1 + 1'b1;
                  state_back <= MODE1;
                  case(cnt_mode1)
                      4'd0:   begin state <= START; end   //I2C通信時序中的START
                      4'd1:   begin data_wr <= dev_addr<<1; state <= WRITE; end   //設備地址
                      4'd2:   begin data_wr <= reg_addr; state <= WRITE; end  //寄存器地址
                      4'd3:   begin data_wr <= reg_data; state <= WRITE; end  //寫入數據
                      4'd4:   begin state <= STOP; end    //I2C通信時序中的STOP
                      4'd5:   begin state <= MAIN; end    //返回MAIN
                      default: state <= IDLE; //如果程序失控,進入IDLE自復位狀態
                  endcase
              end

          同理兩字節數據連讀的操作也做成一個狀態

          I2C讀字操作

          程序實現如下:

          MODE2:begin //兩次讀操作
                  if(cnt_mode2 >= 4'd10) cnt_mode2 <= 1'b0;   //對START中的子狀態執行控制cnt_start
                  else cnt_mode2 <= cnt_mode2 + 1'b1;
                  state_back <= MODE2;
                  case(cnt_mode2)
                      4'd0:   begin state <= START; end   //I2C通信時序中的START
                      4'd1:   begin data_wr <= dev_addr<<1; state <= WRITE; end   //設備地址
                      4'd2:   begin data_wr <= reg_addr; state <= WRITE; end  //寄存器地址
                      4'd3:   begin state <= START; end   //I2C通信時序中的START
                      4'd4:   begin data_wr <= (dev_addr<<1)|8'h01; state <= WRITE; end//設備地址
                      4'd5:   begin ack <= ACK; state <= READ; end    //讀寄存器數據
                      4'd6:   begin dat_l <= data_r; end
                      4'd7:   begin ack <= NACK; state <= READ; end   //讀寄存器數據
                      4'd8:   begin dat_h <= data_r; end
                      4'd9:   begin state <= STOP; end    //I2C通信時序中的STOP
                      4'd10:  begin state <= MAIN; end    //返回MAIN
                      default: state <= IDLE; //如果程序失控,進入IDLE自復位狀態
                  endcase
              end

          因為用到延時,也設計成一個狀態,程序實現如下:

          DELAY:begin //延時模塊
                  if(cnt_delay >= num_delay) begin
                      cnt_delay <= 1'b0;
                      state <= MAIN; 
                  end else cnt_delay <= cnt_delay + 1'b1;
              end

          最后我們編程控制狀態機按照驅動例程代碼中流程運行,程序實現如下:

          4'd0:   begin dev_addr<=7'h39;reg_addr<=8'h80|8'h00;reg_data<=8'h00;state<=MODE1; end 
          4'd1:   begin dev_addr<=7'h39;reg_addr<=8'h80|8'h01;reg_data<=8'hff;state<=MODE1; end 
          4'd2:   begin dev_addr<=7'h39;reg_addr<=8'h80|8'h02;reg_data<=8'hff;state<=MODE1; end 
          4'd3:   begin dev_addr<=7'h39;reg_addr<=8'h80|8'h03;reg_data<=8'hff;state<=MODE1; end 
          4'd4:   begin dev_addr<=7'h39;reg_addr<=8'h80|8'h0e;reg_data<=8'h01;state<=MODE1; end 
          4'd5:   begin dev_addr<=7'h39;reg_addr<=8'h80|8'h0f;reg_data<=8'h20;state<=MODE1; end 
          4'd6:   begin dev_addr<=7'h39;reg_addr<=8'h80|8'h00;reg_data<=8'h0f;state<=MODE1; end 
          4'd7:   begin state <= DELAY; dat_valid <= 1'b0; end    //12ms延時
          4'd8:   begin dev_addr <= 7'h39; reg_addr <= 8'ha0|8'h14;  state <= MODE2; end 
          4'd9:   begin ch0_dat <= {dat_h,dat_l}; end //讀取數據
          4'd10:  begin dev_addr <= 7'h39; reg_addr <= 8'ha0|8'h16;  state <= MODE2; end 
          4'd11:  begin ch1_dat <= {dat_h,dat_l}; end //讀取數據
          4'd12:  begin dev_addr <= 7'h39; reg_addr <= 8'ha0|8'h18;  state <= MODE2; end  
          4'd13:  begin prox_dat <= {dat_h,dat_l}; end    //讀取數據
          4'd14:  begin dat_valid <= 1'b1; end    //讀取數據
          系統總體實現

          程序中我們做了一個簡單的濾波處理,為了保證數據的有效,將瞬間變化太大的采樣數據舍棄,程序實現如下:

          reg [15:0] prox_dat0,prox_dat1,prox_dat2;
          always @(posedge dat_valid) begin
              prox_dat0 <= prox_dat;
              prox_dat1 <= prox_dat0;
              if(((prox_dat1-prox_dat0) >= 16'h200)||((prox_dat1-prox_dat0) >= 16'h200)) prox_dat2 <= prox_dat2;
              else prox_dat2 <= prox_dat0;
              end

          我們從傳感器讀取的距離信息為16位數據,有效范圍Full Scale ADC Counts為0~1023,對應0到16‘h3ff,可以設置一個閾值,當采樣回來的數據與閾值比較控制手機屏幕的顯示與否,本實驗要求用能量條的方式顯示距離的遠近,我們設計一個編碼器將0到16‘h3ff的范圍控制8個led燈的控制,程序實現如下:

          always@(prox_dat2[9:7])
              case (prox_dat2[9:7])
                  3'b000: Y_out = 8'b11111110;
                  3'b001: Y_out = 8'b11111100;
                  3'b010: Y_out = 8'b11111000;
                  3'b011: Y_out = 8'b11110000;
                  3'b100: Y_out = 8'b11100000;
                  3'b101: Y_out = 8'b11000000;
                  3'b110: Y_out = 8'b10000000;
                  3'b111: Y_out = 8'b00000000;
                  default:Y_out = 8'b11111111;
              endcase

          在頂層設計中例化兩個模塊,將信號連接,程序實現如下:

          wire dat_valid;
          wire [15:0] ch0_dat, ch1_dat, prox_dat;
          APDS_9901_Driver u1(
          .clk            (clk            ),  //系統時鐘
          .rst_n          (rst_n          ),  //系統復位,低有效
          .i2c_scl        (i2c_scl        ),  //I2C總線SCL
          .i2c_sda        (i2c_sda        ),  //I2C總線SDA
          .dat_valid      (dat_valid      ),  //數據有效脈沖
          .ch0_dat        (ch0_dat        ),  //ALS數據
          .ch1_dat        (ch1_dat        ),  //IR數據
          .prox_dat       (prox_dat       )   //Prox數據
          ); 
          Decoder u2(.dat_valid      (dat_valid      ),
          .prox_dat       (prox_dat       ),
          .Y_out          (led            )
          );

          綜合后的設計框圖如下:

          RTL設計框圖

          實驗步驟

          1. 雙擊打開Quartus Prime工具軟件;
          2. 新建工程:File → New Project Wizard(工程命名,工程目錄選擇,設備型號選擇,EDA工具選擇);
          3. 新建文件:File → New → Verilog HDL File,鍵入設計代碼并保存;
          4. 設計綜合:雙擊Tasks窗口頁面下的Analysis & Synthesis對代碼進行綜合;
          5. 管腳約束:Assignments → Assignment Editor,根據項目需求分配管腳;
          6. 設計編譯:雙擊Tasks窗口頁面下的Compile Design對設計進行整體編譯并生成配置文件;
          7. 程序燒錄:點擊Tools → Programmer打開配置工具,Program進行下載;
          8. 觀察設計運行結果。

          實驗現象

          將設計加載到FPGA,手指在接近光傳感器上下移動,觀察核心板上8個LED燈的狀態,APDS-9901還是環境光傳感器,有興趣的同學可以嘗試一下其他應用。

          實驗現象示意



          評論


          相關推薦

          技術專區

          關閉
          看屁屁www成人影院,亚洲人妻成人图片,亚洲精品成人午夜在线,日韩在线 欧美成人 (function(){ var bp = document.createElement('script'); var curProtocol = window.location.protocol.split(':')[0]; if (curProtocol === 'https') { bp.src = 'https://zz.bdstatic.com/linksubmit/push.js'; } else { bp.src = 'http://push.zhanzhang.baidu.com/push.js'; } var s = document.getElementsByTagName("script")[0]; s.parentNode.insertBefore(bp, s); })();