一、介紹
1、原理
採用940nm垂直腔面發射激光器(Vertical-Cavity Surface-Emitting Laser,簡稱VCSEL)發射出激光,激光碰到障礙物後反射回來被VL53L0X接收到,測量激光在空氣中的傳播時間,進而得到距離。VCSEL相關知識
2、參數
超小體積:4.4 x 2.4 x 1.0mm
最大測距:2m
發射的激光對眼鏡安全,且完全不可見。
工作電壓:2.6 to 3.5 V
通信方式:IIC,400KHz,設備地址0x52,最低位是讀寫標誌位。0表示寫,1表示讀。
因此進行寫的時候,此8位數據爲:0101 0010,即0x52。進行讀的時候,此8位數據爲:0101 0011,即0x53。
3、框圖
VL53L0X上有兩個孔,一個是VCSEL激光發射孔,一個是SPAD激光檢測陣列的孔。
1腳在大孔那邊。
4、引腳圖
1腳AVDDVCSEL:VCSEL電源正
2腳AVSSVCSEL:VCSEL電源地
3腳、4腳、6腳、12腳GND:地
5腳XSHUT:電源模式控制,如果不需要休眠功能,此腳可以直接接到AVDD上。
7腳GPIO1:中斷輸出。開漏輸出,因此必須要外部上拉。
8腳DNC:懸空
9腳和10腳:IIC通信端口
11腳AVDD:電源正。
5、原理圖
二、固件狀態機
ST爲VL53L0X專門配備了一套API,直接封裝了各種功能比如:初始化/校準、測距開始/停止、精度選擇、測距模式選擇。用戶的應用程序調用API中的函數,然後API通過IIC與VL53L0X中的固件(Firmware)進行通信,固件再操作硬件。
VL53L0X中的固件是按照狀態機來進行工作的:
當上電,VL53L0X會進入Hw Standby狀態,這是待機狀態,功耗很低。然後拉高XSHUT引腳,讓VL53L0X進入Fw Boot狀態,開始準備測距。如果不需要待機狀態,可以把XSHUT接到AVDD上。VL53L0X處於休眠狀態是不能進行IIC通信的。
VL53L0X有3種工作模式:
(1)單次模式:收到測距開始命令後,開始進行測量,測量完成後自動退出,進入Sw Standby狀態。
(2)連續模式:收到測距開始命令後,就一直進行測量,直到收到測距停止命令。收到測距停止命令時,會把最後一次測量完成才退出。
(3)連續間隔模式:收到測距開始命令後,開始進行測量,完成一次測量後,等待一段時間再進行下次測量,直到收到測距停止命令。測量等待間隔時間可調。
三、加蓋玻片
VL53L0X一般會和蓋玻片一起結合使用。蓋玻片有兩個作用:提供物理保護,防止灰塵;對光進行濾波。
蓋玻片通常是不透明的,有兩個圓孔或一個橢圓孔,以便發出和接收紅外線。蓋玻片必須滿足一些光學要求,以保證測距能力。通過透射係數和霧度係數來測量蓋玻片的質量。
有兩個參數需要注意:VL53L0X和蓋板窗口之間的氣隙以及VL53L0X前面的擴展區(exclusion area),如下圖:
當激光穿過蓋玻片時,有部分會發生反射,我們應該儘量減少反射的光。嵌入的顆粒/孔洞和/或粗糙表面是蓋玻片中光散射的主要因素。
理想的蓋玻片有如下特點:
(1)塑料或玻璃材料無結構缺陷
(2)無可導致指紋光散射或污跡敏感的表面缺陷
(3)在近紅外(940nm±10nm)和低霧度條件下,透過率>90%
(4)不降低指紋免疫性的外塗層(抗指紋或抗反射塗層)
(5)單一材料。使用雙重材料可能會改變性能。
理想的結構設計(蓋玻片蓋在VL53L0X上的結構)有如下特點:
(1)氣隙小(<0.5 mm)
(2)蓋玻片薄
(3)蓋玻片與VL53L0X表明的傾角低於2度
(4)嚴格的公差。
蓋玻片的質量對激光傳輸的影響:
推薦的結構設計:儘量小的空氣間隙(下圖中的‘E’),和薄的高透射係數的蓋玻片(下圖中的‘D’)
如果空氣間隙和蓋玻片厚度已經不能再減小,可以在間隙中加入墊片,墊片可以幫助減少串擾。(串擾後面會講)
蓋玻片還必須要和VL53L0X平行
四、校準流程
爲保證精度,用戶確定好自己的使用環境(是否要蓋玻璃蓋、使用環境溫度、供電電壓等)後,要進行一次校準。流程如下:
1、溫度校準是確定兩個與溫度相關的參數:VHV 和 phase cal 。當每次VL53L0X的使用環境與校準環境之間溫差大於8度時,都需要重新校準。
2、Offset校準是校準時間距離與測量距離之間的偏移量,一般推薦以10cm來進行校準。偏移量一般都是一個固定值,當確定好供電電壓、環境溫度、是否加玻璃蓋等之後,讀出測量值與實際值做差即可得到偏移量。
3、CrossTalk校準:CrossTalk即串擾,它被定義爲從蓋玻片彈回來的信號。如果加了玻璃蓋,當激光射出玻璃蓋的時候,一部分激光會被反射回來,成爲干擾信號。干擾信號的大小取決於蓋玻片類型和氣隙大小。干擾信號產生的距離誤差大小正比於串擾大小與目標返回信號大小的比值。
五、測距配置(profiles)
根據用戶的應用,可以把VL53L0X配置爲4種測距配置(ranging profiles):默認配置、高速測距配置、高精度測距配置、長距離測距配置。VL53L0X以何種配置工作,並不是某一個寄存器決定的,而是以初始化的時候寫入的衆多初始參數決定的,因此把這些參數根據應用需求調整一下,又可以得到新的測距配置。
4種測距配置的特點如下:
不管以何種配置工作,VL53L0X每次從上電到進入測距狀態,總共需要3個階段,如下:
1、初始化和校準階段:處於設備復位後和開始第一次測量前,如果用戶的使用環境溫度變化大,需要週期性進行溫度校準。
2、測距階段:VL53L0X會發射幾個激光脈衝,然後被目標反射回來,再被SPAD陣列接收到進行測距。
3、數據處理階段:VL53L0X計算測量距離,如果信號太弱或沒有目標,VL53L0X會返回錯誤碼。VL53L0X會處理如下工作:信號強弱檢查、Offset矯正、CrossTalk矯正、距離值計算。爲了進一步提高精度,用戶可以自己對讀取到的距離值進行:多次取平均、遲滯處理、濾波處理。
六、測距
1、用戶可以通過輪詢或中斷的方式來獲取數據
(1)輪詢:需要主動去讀API函數,獲取測量狀態。
(2)中斷:當一次測量完成後,VL53L0X通過GPIO1引腳發送中斷信號。
2、測距過程
3、IIC寫1字節數據
4、IIC讀1字節數據
5、IIC寫多個字節數據
6、IIC讀多個字節數據
七、API移植
API包解壓後如下:
API包的doc文件夾下有對API中所有結構體、函數、宏定義的介紹,要使用哪些函數,怎麼使用,都可以去查詢,提供pdf和chm版本:
下面開始移植:
建立一個乾淨的工程,在工程文件夾下新建一個VL53L0X文件夾,把API包裏的Api文件夾下的兩個文件夾複製進去。
打開工程,新建一個VL53L0X目錄,添加如下文件:
再把頭文件包含進去:
編譯一下,會在vl53l0x_i2c_win_serial_comms.c和vl53l0x_platform.c中報錯。
vl53l0x_i2c_win_serial_comms.c是IIC的硬件實現層,其中實現了IIC的初始化、IIC關閉、讀數據、寫數據,共4個功能。現在移植到STM32平臺後,要根據自己的硬件重新改。首先把相關的函數內容刪除:
int VL53L0X_i2c_init(char *comPortStr, unsigned int baudRate) // mja
{
unsigned int status = STATUS_FAIL;
return status;
}
int32_t VL53L0X_comms_close(void)
{
unsigned int status = STATUS_FAIL;
return status;
}
int32_t VL53L0X_write_multi(uint8_t address, uint8_t reg, uint8_t *pdata, int32_t count)
{
int32_t status = STATUS_OK;
return status;
}
int32_t VL53L0X_read_multi(uint8_t address, uint8_t index, uint8_t *pdata, int32_t count)
{
int32_t status = STATUS_OK;
return status;
}
vl53l0x_platform.c中有一個延時函數報錯,同樣把它的內容刪除:
VL53L0X_Error VL53L0X_PollingDelay(VL53L0X_DEV Dev){
VL53L0X_Error status = VL53L0X_ERROR_NONE;
LOG_FUNCTION_START("");
LOG_FUNCTION_END(status);
return status;
}
把其他的小錯誤排除後,再次編譯,就可以通過了。
接下來把IIC的4個函數根據自己的硬件重新寫好,新建vl53l0x_IIC.c和vl53l0x_IIC.h文件添加進工程中。vl53l0x_IIC.c內容如下:
#include "vl53l0x_IIC.h"
void VL53L0X_IIC_Init(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
RCC_APB2PeriphClockCmd( VL_SDA_RCC, ENABLE );
GPIO_InitStructure.GPIO_Pin = VL_SDA_PIN; //端口配置
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP ; //推輓輸出
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; //50Mhz速度
GPIO_Init(VL_SDA_IOx, &GPIO_InitStructure);
RCC_APB2PeriphClockCmd( VL_SCL_RCC, ENABLE );
GPIO_InitStructure.GPIO_Pin = VL_SCL_PIN; //端口配置
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP ; //推輓輸出
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; //50Mhz速度
GPIO_Init(VL_SCL_IOx, &GPIO_InitStructure);
GPIO_SetBits(VL_SDA_IOx,VL_SDA_PIN);//SDA輸出高
GPIO_SetBits(VL_SCL_IOx,VL_SCL_PIN);//SCL輸出高
}
void VL_IIC_Start(void)
{
VL_SDA_OUT();//sda線輸出
VL_IIC_SDA=1;
VL_IIC_SCL=1;
delay_us(4);
VL_IIC_SDA=0;//START:when CLK is high,DATA change form high to low
delay_us(4);
VL_IIC_SCL=0;//鉗住I2C總線,準備發送或接收數據
}
//產生IIC停止信號
void VL_IIC_Stop(void)
{
VL_SDA_OUT();//sda線輸出
VL_IIC_SCL=0;
VL_IIC_SDA=0;//STOP:when CLK is high DATA change form low to high
delay_us(4);
VL_IIC_SCL=1;
VL_IIC_SDA=1;//發送I2C總線結束信號
delay_us(4);
}
u8 VL_IIC_Wait_Ack(void)
{
u8 ucErrTime=0;
VL_SDA_IN(); //SDA設置爲輸入
VL_IIC_SDA=1;delay_us(1);
VL_IIC_SCL=1;delay_us(1);
while(VL_READ_SDA)
{
ucErrTime++;
if(ucErrTime>250)
{
VL_IIC_Stop();
return 1;
}
}
VL_IIC_SCL=0;//時鐘輸出0
return 0;
}
//產生ACK應答
void VL_IIC_Ack(void)
{
VL_IIC_SCL=0;
VL_SDA_OUT();
VL_IIC_SDA=0;
delay_us(2);
VL_IIC_SCL=1;
delay_us(2);
VL_IIC_SCL=0;
}
//不產生ACK應答
void VL_IIC_NAck(void)
{
VL_IIC_SCL=0;
VL_SDA_OUT();
VL_IIC_SDA=1;
delay_us(2);
VL_IIC_SCL=1;
delay_us(2);
VL_IIC_SCL=0;
}
//IIC發送一個字節
//返回從機有無應答
//1,有應答
//0,無應答
void VL_IIC_Send_Byte(u8 txd)
{
u8 t;
VL_SDA_OUT();
VL_IIC_SCL=0;//拉低時鐘開始數據傳輸
for(t=0;t<8;t++)
{
if((txd&0x80)>>7)
VL_IIC_SDA=1;
else
VL_IIC_SDA=0;
txd<<=1;
delay_us(2);
VL_IIC_SCL=1;
delay_us(2);
VL_IIC_SCL=0;
delay_us(2);
}
}
//讀1個字節,ack=1時,發送ACK,ack=0,發送nACK
u8 VL_IIC_Read_Byte(void)
{
unsigned char i,receive=0;
VL_SDA_IN();//SDA設置爲輸入
VL_IIC_SDA = 1;
delay_us(4);
for(i=0;i<8;i++ )
{
receive<<=1;
VL_IIC_SCL=0;
delay_us(4);
VL_IIC_SCL=1;
delay_us(4);
if(VL_READ_SDA)
receive |= 0x01;
delay_us(4); //1
}
VL_IIC_SCL = 0;
return receive;
}
//IIC寫一個字節數據
u8 VL_IIC_Write_1Byte(u8 SlaveAddress, u8 REG_Address,u8 REG_data)
{
VL_IIC_Start();
VL_IIC_Send_Byte(SlaveAddress);
if(VL_IIC_Wait_Ack())
{
VL_IIC_Stop();//釋放總線
return 1;//沒應答則退出
}
VL_IIC_Send_Byte(REG_Address);
VL_IIC_Wait_Ack();
delay_us(5);
VL_IIC_Send_Byte(REG_data);
VL_IIC_Wait_Ack();
VL_IIC_Stop();
return 0;
}
//IIC讀一個字節數據
u8 VL_IIC_Read_1Byte(u8 SlaveAddress, u8 REG_Address,u8 *REG_data)
{
VL_IIC_Start();
VL_IIC_Send_Byte(SlaveAddress);//發寫命令
if(VL_IIC_Wait_Ack())
{
VL_IIC_Stop();//釋放總線
return 1;//沒應答則退出
}
VL_IIC_Send_Byte(REG_Address);
VL_IIC_Wait_Ack();
delay_us(5);
VL_IIC_Start();
VL_IIC_Send_Byte(SlaveAddress|0x01);//發讀命令
VL_IIC_Wait_Ack();
*REG_data = VL_IIC_Read_Byte();
VL_IIC_Stop();
return 0;
}
//I2C讀多個字節數據
uint8_t VL_I2C_Read_nByte(uint8_t SlaveAddress, uint8_t REG_Address, uint8_t *buf, uint16_t len)
{
VL_IIC_Start();
VL_IIC_Send_Byte(SlaveAddress);//發寫命令
if(VL_IIC_Wait_Ack())
{
VL_IIC_Stop();//釋放總線
return 1;//沒應答則退出
}
VL_IIC_Send_Byte(REG_Address);
VL_IIC_Wait_Ack();
delay_us(5);
VL_IIC_Start();
VL_IIC_Send_Byte(SlaveAddress|0x01);//發讀命令
VL_IIC_Wait_Ack();
while(len)
{
*buf = VL_IIC_Read_Byte();
if(1 == len)
{
VL_IIC_NAck();
}
else
{
VL_IIC_Ack();
}
buf++;
len--;
}
VL_IIC_Stop();
return STATUS_OK;
}
//I2C寫多個字節數據
uint8_t VL_I2C_Write_nByte(uint8_t SlaveAddress, uint8_t REG_Address, uint8_t *buf, uint16_t len)
{
VL_IIC_Start();
VL_IIC_Send_Byte(SlaveAddress);//發寫命令
if(VL_IIC_Wait_Ack())
{
VL_IIC_Stop();//釋放總線
return 1;//沒應答則退出
}
VL_IIC_Send_Byte(REG_Address);
VL_IIC_Wait_Ack();
while(len--)
{
VL_IIC_Send_Byte(*buf++);
VL_IIC_Wait_Ack();
}
VL_IIC_Stop();
return STATUS_OK;
}
vl53l0x_IIC.h內容如下:
#ifndef __VL53L0X_IIC_H
#define __VL53L0X_IIC_H
#include "stdint.h"
#include "sys.h"
#include "delay.h"
#define VL_SDA_RCC RCC_APB2Periph_GPIOB
#define VL_SDA_PIN GPIO_Pin_11
#define VL_SDA_IOx GPIOB
#define VL_SCL_RCC RCC_APB2Periph_GPIOB
#define VL_SCL_PIN GPIO_Pin_10
#define VL_SCL_IOx GPIOB
#define VL_SDA_IN() {GPIOB->CRH&=0xFFFF0FFF;GPIOB->CRH|=8<<12;}
#define VL_SDA_OUT() {GPIOB->CRH&=0xFFFF0FFF;GPIOB->CRH|=3<<12;}
#define VL_IIC_SCL PBout(10) //SCL
#define VL_IIC_SDA PBout(11) //SDA
#define VL_READ_SDA PBin(11) //輸入SDA
//Status
#define STATUS_OK 0x00
#define STATUS_FAIL 0x01
void VL53L0X_IIC_Init(void);
uint8_t VL_I2C_Read_nByte(uint8_t SlaveAddress, uint8_t REG_Address, uint8_t *buf, uint16_t len);
uint8_t VL_I2C_Write_nByte(uint8_t SlaveAddress, uint8_t REG_Address, uint8_t *buf, uint16_t len);
#endif
於是vl53l0x_i2c_win_serial_comms.c中的4個底層函數改寫爲:
int VL53L0X_i2c_init(char *comPortStr, unsigned int baudRate) // mja
{
unsigned int status = STATUS_FAIL;
return status;
}
int32_t VL53L0X_comms_close(void)
{
unsigned int status = STATUS_FAIL;
return status;
}
int32_t VL53L0X_write_multi(uint8_t address, uint8_t reg, uint8_t *pdata, int32_t count)
{
int32_t status = STATUS_OK;
if(VL_I2C_Write_nByte(address,reg,pdata,count))
status = STATUS_FAIL;
return status;
}
int32_t VL53L0X_read_multi(uint8_t address, uint8_t index, uint8_t *pdata, int32_t count)
{
int32_t status = STATUS_OK;
if(VL_I2C_Read_nByte(address,index,pdata,count))
status = STATUS_FAIL;
return status;
}
八、API使用
1、校準
爲獲得更好的精度,當使用環境確定後,要校準一次。校準的過程中,需要主機把校準的數據保存下來,完成校準後在後面的使用過程中只要環境變化不大,都可以直接使用校準數據即可,不需要每次都校準。
下面的校準過程必須按照順序一步一步來,不能顛倒。
(1)Data init
調用VL53L0X_DataInit()函數一次,設備上電後調用一次。把VL53L0X_State從VL53L0X_STATE_POWERDOWN改爲 VL53L0X_STATE_WAIT_STATICINIT。VL53L0X_State是初始化狀態機,看此變量的值可以就可以知道當前的初始化進度。
(2)Static Init
調用VL53L0X_StaticInit()函數來做基本的設備初始化。把VL53L0X_State從VL53L0X_STATE_WAIT_STATICINIT改爲VL53L0X_STATE_IDLE.
(3)參考SPADs校準(Reference SPADs calibration )
參考SPADs校準會在從ST出廠的最後一次模塊測試時在裸VL53L0X(沒有蓋玻片)上進行,校準數據(SPAD數量和類型)會被存入VL53L0X的Flash。
如果用戶要加蓋玻片使用,就需要重新校準參考SPADs。
校準的數據(SPAD數量和類型),VL53L0X會自動保存到自己的Flash中,用戶可以讀出來,然後直接賦值給VL53L0X來初始化。用戶也可以把校準數據保存到單片機的Flash中,然後賦值給VL53L0X來初始化。總之,參考SPADs只需要校準一次,然後把校準數據保存好,以後直接賦值即可,不需要每次都校準。
校準過程不需要任何條件,直接調用VL53L0X_PerformRefSpadManagement()函數即可,此函數會輸出校準數據(SPADs的數量和類型),當獲取到這兩個值後,VL53L0X會自動編程到自己的Flash中。同時主機也要保存這兩個參數用於以後的參數初始化。
雖然校準過程不需要目標物或者光照條件,但是如果放置一個高反射率的目標物體在VL53L0X前面,導致過多的激光被檢測到,會導致校準失敗,報'-50'狀態碼。出現這種情況,要把目標物體移開再校準。
函數詳情:VL53L0X_API VL53L0X_Error VL53L0X_PerformRefSpadManagement (VL53L0X_DEV Dev, uint32_t * refSpadCount, uint8_t * isApertureSpads)
函數功能:參調用此函數確定要啓用的參考SPAD的最小數量,以實現對目標的最佳測量。此函數同時也應該在初始化期間執行一次。
(4)溫度校準(Ref (temperature) calibration)
溫度校準會輸出與溫度相關的兩個參數:VHV 和 phase cal。這兩個參數還可以用來設置VL53L0X的靈敏度。
當溫度變化大於8度,建議重新校準。如果溫度沒變,校準數據可以直接加載使用。
溫度校準也不需要任何條件,直接調用VL53L0X_PerformRefCalibration()函數即可。
函數詳情:VL53L0X_API VL53L0X_Error VL53L0X_PerformRefCalibration (VL53L0X_DEV Dev, uint8_t * pVhvSettings, uint8_t * pPhaseCal)
(5)偏移校準(Offset calibration)
偏移校準也是在從ST出廠的最後一次模塊測試時在裸VL53L0X(沒有蓋玻片)上進行的,校準數據(偏移值)會被存入VL53L0X的Flash。
當VL53L0X的使用環境改變(溫度、加了蓋玻片),可能會出現很大的偏差,就需要重新校準偏移值。
偏移校準所需條件:使用白色目標物體(88%反射率)、把目標物體放置在100mm處(實際的距離)、在黑暗環境中。100mm的校準距離可以根據用戶的應用作出調整,但必須在測距曲線的線性部分進行選擇,因爲偏移值應該是一個常數,如果處於非線性區,偏移值就不好確定了。
準備好條件後,調用VL53L0X_PerformOffsetCalibration()函數。函數需要輸入校準距離。函數輸出的就是偏移值,單位是mm。把校準值保存到單片機的Flash中,以後直接賦值即可。
需要注意的是,函數的輸入校準距離要左移16位,比如校準距離值爲100mm:
Status=VL53L0X_PerformOffsetCalibration(pMyDevice,100<<16,&OffsetMicroMeter); //偏移校準,把白色物體放在100mm處進行
if(Status!=VL53L0X_ERROR_NONE) { return Status; }
校準完成後,OffsetMicroMeter變量的值就是偏移值,偏移校準完成後,再去讀測量距離值就變成100了,即傳入函數的校準距離值,可以根據這個來判斷偏移校準是否成功。
函數詳情:VL53L0X_API VL53L0X_Error VL53L0X_PerformOffsetCalibration (VL53L0X_DEV Dev, FixPoint1616_t CalDistanceMilliMeter, int32_t * pOffsetMicroMeter)
(6)蓋玻片校準(Cross-talk calibration)
蓋玻片對測距的影響如上,LowXtalk是指VL53L0X與蓋玻片之間空氣間隙的大小,可見間隙越大,影響越大。上圖中的綠點線是理想測距線。
對於蓋玻片的校準就是對測量距離值進行加權放大,氣隙越小矯正效果越好,如果已經是上圖綠線那樣了,就難了。
要想開始校準過程,就先要確定好校準距離。校準距離區間的開始點一般是測量值開始偏離出理想測距線的時候。結束點一般是信號太弱,雖然實際距離在增加,測量距離卻開始減小的時候。如下:
左圖中可以看到藍線在A點開始大幅度偏離綠點線,在B點實際距離在增加,測量距離卻要開始減小了。右圖同樣。因此選擇A點和B點間的任何一個距離作爲校準距離即可。
如果校準距離選在了線性區域,校準因數會太小,導致矯正沒有效果。
要進行蓋玻片校準,用戶需要在校準前把測量距離與實際距離的關係圖繪製出來,然後選擇好校準距離。目標物體要使用17%反射率的灰色物體。下圖爲筆者測得的數據,校準距離選擇450或500。
做好準備後調用VL53L0X_PerformXTalkCalibration()函數,函數的輸入參數就是校準距離,輸出值是蓋玻片校準因數,要把校準因數保存到主機中,以便於以後使用。
函數的輸入參數依然要左移16位,比如校準距離450:
Status=VL53L0X_PerformXTalkCalibration(pMyDevice,450<<16,&XTalkCompensation); //蓋玻片校準,把灰色物體放在校準距離處進行
if(Status!=VL53L0X_ERROR_NONE) { return Status; }
校準完成後,把目標物體放在450mm處,測量的距離就應該是450mm了,也可以以此來判斷校準是否成功。
函數詳情:VL53L0X_API VL53L0X_Error VL53L0X_PerformXTalkCalibration (VL53L0X_DEV Dev, FixPoint1616_t XTalkCalDistance, FixPoint1616_t * pXTalkCompensationRateMegaCps)
總結:
1、VL53L0X有4種校準:參考SPADs校準、溫度校準、偏移校準、蓋玻片校準。
2、溫度校準不需要任何條件,而且溫度最容易改變,因此可以每次上電都校準一次。
3、確定好自己產品上給VL53L0X提供的電壓值、需不需要蓋玻片後,可以拿一臺出來進行參考SPADs校準、偏移校準、蓋玻片校準,然後把校準參數取出來做成常量直接放到程序中,給其他的產品直接賦值即可,這樣就不用每臺都進行繁瑣的校準過程了。
2、上電開始測距
校準完成後,以後每次上電只需要3個階段來進入測距狀態:初始化和加載校準數據階段、測距階段、數據處理階段。
1)、初始化和加載校準數據階段
(1)Data init
調用VL53L0X_DataInit()函數一次。
(2)Static Init
調用VL53L0X_StaticInit()函數做基本初始化。
(3)設置參考SPADs校準值(Reference SPADs)
調用VL53L0X_SetReferenceSpads() 用於設置參考SPADs的數量和類型。
函數詳情:VL53L0X_API VL53L0X_Error VL53L0X_SetReferenceSpads (VL53L0X_DEV Dev, uint32_t refSpadCount, uint8_t isApertureSpads)
VL53L0X_GetReferenceSpads()用於讀取SPADs的數量和類型,如果設備參數結構體沒有值,就會去VL53L0X的Flash中去讀。
函數詳情:VL53L0X_API VL53L0X_Error VL53L0X_GetReferenceSpads (VL53L0X_DEV Dev, uint32_t * refSpadCount, uint8_t * isApertureSpads)
(4)設置溫度校準值(Ref calibration)
調用VL53L0X_SetRefCalibration()函數,設置溫度校準數據。
函數詳情:VL53L0X_API VL53L0X_Error VL53L0X_SetRefCalibration (VL53L0X_DEV Dev, uint8_t VhvSettings, uint8_t PhaseCal)
(5)設置偏移校準值(Offset calibration)
調用VL53L0X_SetOffsetCalibrationDataMicroMeter()函數設置偏移值,單位mm。
函數詳情:VL53L0X_API VL53L0X_Error VL53L0X_SetOffsetCalibrationDataMicroMeter (VL53L0X_DEV Dev, int32_t OffsetCalibrationDataMicroMeter)
(6)設置蓋玻片校準值(Cross-talk correction)
調用VL53L0X_SetXTalkCompensationRateMegaCps()設置蓋玻片校準因數,再調用VL53L0X_SetXTalkCompensationEnable()函數來使能蓋玻片矯正測量距離值。
函數詳情:VL53L0X_API VL53L0X_Error VL53L0X_SetXTalkCompensationRateMegaCps (VL53L0X_DEV Dev, FixPoint1616_t XTalkCompensationRateMegaCps)
函數詳情:VL53L0X_API VL53L0X_Error VL53L0X_SetXTalkCompensationEnable (VL53L0X_DEV Dev, uint8_t XTalkCompensationEnable)
2)、測距階段
(1)測距模式
調用VL53L0X_SetDeviceMode()來設置工作模式:單次、連續、間隔連續。
調用VL53L0X_GetDeviceMode()來獲取當前工作模式。
(2)輪詢或中斷
一旦測距完成,VL53L0X會輸出一箇中斷信號或置位狀態寄存器。
VL53L0X_SetGPIOConfig()用於設置中斷模式。
(3)開始測距
A、調用VL53L0X_StartMeasurement()函數,VL53L0X會按照之前設置的測距模式開始測距。
B、調用VL53L0X_PerformSingleMeasurement()函數,VL53L0X會開始測距並且等待測距完成。此函數內部調用了VL53L0X_StartMeasurement()和VL53L0X_GetMeasurementDataReady()。
C、調用VL53L0X_PerformSingleRangingMeasurement()函數,VL53L0X會開始測距,然後等待測距完成,接着清除中斷,最後輸出測量數據退出。此函數內部調用了VL53L0X_PerformSingleMeasurement()、VL53L0X_GetRangingMeasurementData()
和VL53L0X_ClearInterruptMask()。
(4)停止測距
調用VL53L0X_StopMeasurement()函數來停止測距。
(5)獲取測量數據
調用VL53L0X_GetMeasurementDataReady()函數來獲取測量狀態。
調用VL53L0X_GetRangingMeasurementData()函數來獲取測量數據,此函數返回一個buffer,包含如下內容:
RangeMilliMeter:以mm爲單位的測量距離。
RangeDMaxMilliMeter:最大測量距離,mm爲單位。
SignalRateRtnMegaCps:返回的信號大小(signal rate (MCPS)),由目標物的反射率決定。
AmbientRateRtnMegaCps:返回的環境信號大小(ambient rate (MCPS)),由環境光決定。
EffectiveSpadRtnCount
RangeStatus:測量狀態
其中RangeStatus有如下取值:
要獲取測距狀態的字符串(即上表中的第二列),可以調用VL53L0X_GetRangeStatusString()函數。
L53L0X_GetDeviceErrorStatus()函數只能在調試過程中使用,一般不要用。
測量數據中的 SignalRateRtnMegaCps 是經過蓋玻片矯正後的數據(如果使能了蓋玻片矯正),如果要得到沒矯正的數據,調用VL53L0X_GetTotalSignalRate()函數。
3、API的其他功能
(1)調整總體測量時間
調用VL53L0X_SetMeasurementTimingBudgetMicroSeconds()和VL53L0X_GetMeasurementTimingBudgetMicroSeconds()來設置或獲取總體測量時間。
總體測量時間默認爲33ms,最小值爲20ms。如:
Status =VL53L0X_SetMeasurementTimingBudgetMicroSeconds(pMyDevice,66000)就是把總體測量時間設置爲66ms。
增加測量時間會增加測量精度。
(2)限制值設置(Limit settings)
用戶可以啓用/禁用限制檢查和限制值。
禁用或放寬這些限制可以允許更長的測量範圍,在這種情況下,標準偏差將增加,主機將接收測量異常值。
有3個限制值可以調整:
A、Sigma:VL53L0X_CHECKENABLE_SIGMA_FINAL_RANGE
Sigma是參考和返回SPAD陣列之間的時差(移位)。Sigma本來表示的是光的傳播時間,在芯片內會轉換成距離,所以這個參數是用mm表示的。
B、Return Signal Rate:VL53L0X_CHECKENABLE_SIGNAL_RATE_FINAL_RANGE
返回信號大小的測量值,用MCPS表示。這表示從目標反射並由設備檢測到的信號的幅度。
C、Range Ignore Threshold:VL53L0X_CHECKENABLE_RANGE_IGNORE_THRESHOLD
接收到信號的最小閾值。信號大小低於此值的測量將被忽略。這可確保不會由於外殼反射而進行錯誤測量。
使用VL53L0X_SetLimitCheckEnable() 和 VL53L0X_GetLimitCheckEnable() 來設置使能或禁止限制值。
使用VL53L0X_SetLimitCheckValue() 和VL53L0X_GetLimitCheckValue()來設置和獲取限制值大小。
使用VL53L0X_GetLimitCheckCurrent() 和 VL53L0X_GetLimitCheckStatus()可以得到當前值和與限制值進行比較的狀態。
限制值的默認使能狀態和值:
使用舉例:
Status = VL53L0X_SetLimitCheckEnable(pMyDevice,VL53L0X_CHECKENABLE_SIGNAL_RATE_FINAL_RANGE, 1);
Status = VL53L0X_SetLimitCheckValue(pMyDevice,VL53L0X_CHECKENABLE_SIGNAL_RATE_FINAL_RANGE, 0.40*65536);
讀取當前的sigma值:
Status = VL53L0X_GetLimitCheckCurrent(pMyDevice,VL53L0X_CHECKENABLE_SIGMA_FINAL_RANGE, &Sigma);
(3)連續間隔模式的測量間隔時間設置
VL53L0X_SetInterMeasurementPeriodMilliSeconds()和VL53L0X_GetInterMeasurementPeriodMilliSeconds()
(4)API版本和產品版本讀取
VL53L0X_GetVersion()
VL53L0X_GetPalSpecVersion()
VL53L0X_GetProductRevision()
4、API的狀態碼和錯誤碼
調用VL53L0X_GetPalState()來獲取API的狀態值(下表的第一列)。VL53L0X_GetPalStateString()返回API的狀態字符串(下表的第二列)。
調用API函數都會返回API的錯誤碼。VL53L0X_GetPalErrorString()函數返回錯誤字符串。
5、IIC設備地址設置
VL53L0X_SetDeviceAddress()函數可以改變IIC的設備地址
6、復位
VL53L0X_ResetDevice()
7、中斷設置
使用VL53L0X_SetGpioConfig()和VL53L0X_GetGpioConfig()來設置和獲取中斷功能。有如下選項:
A、無中斷。
B、當測量值小於下限時發生中斷
C、當測量值大於上限時發生中斷
D、當測量值小於下限或大於上限時發生中斷
當中斷閾值被編程爲大於254mm並且也被設置爲連續或連續定時模式時,在API中嵌入了一個專用的過程。在這種情況下,API在每次測距開始時加載一些特定的調諧參數,這將給第一次測距測量帶來幾毫秒的延遲(取決於主機I2C的性能)。
在單次測量模式中,最大的門限值爲254mm。
VL53L0X_SetInterruptThresholds()和VL53L0X_GetInterruptThresholds()函數用於設置或獲取中斷門限值。
8、4種測距配置(range profiles)的設置
下面列出要把VL53L0X設置爲這4種配置所需要的函數,都要在進入測量前執行:
A、高精度測距配置
if (Status == VL53L0_ERROR_NONE) {
Status = VL53L0_SetLimitCheckValue(pMyDevice,
VL53L0_CHECKENABLE_SIGNAL_RATE_FINAL_RANGE,
(FixPoint1616_t)(0.25*65536));
}
if (Status == VL53L0_ERROR_NONE) {
Status = VL53L0_SetLimitCheckValue(pMyDevice,
VL53L0_CHECKENABLE_SIGMA_FINAL_RANGE,
(FixPoint1616_t)(18*65536));
}
if (Status == VL53L0_ERROR_NONE) {
Status =VL53L0_SetMeasurementTimingBudgetMicroSeconds(pMyDevice,
200000);
}
B、長距離測距配置
if (Status == VL53L0_ERROR_NONE) {
Status = VL53L0_SetLimitCheckValue(pMyDevice,
VL53L0_CHECKENABLE_SIGNAL_RATE_FINAL_RANGE,
(FixPoint1616_t)(0.1*65536));
}
if (Status == VL53L0_ERROR_NONE) {
Status = VL53L0_SetLimitCheckValue(pMyDevice,
VL53L0_CHECKENABLE_SIGMA_FINAL_RANGE,
(FixPoint1616_t)(60*65536));
}
if (Status == VL53L0_ERROR_NONE) {
Status =VL53L0_SetMeasurementTimingBudgetMicroSeconds(pMyDevice,
33000);
}
if (Status == VL53L0_ERROR_NONE) {
Status = VL53L0_SetVcselPulsePeriod(pMyDevice,
VL53L0_VCSEL_PERIOD_PRE_RANGE, 18);
}
if (Status == VL53L0_ERROR_NONE) {
Status = VL53L0_SetVcselPulsePeriod(pMyDevice,
VL53L0_VCSEL_PERIOD_FINAL_RANGE, 14);
}
C、高速測距配置
if (Status == VL53L0_ERROR_NONE) {
Status = VL53L0_SetLimitCheckValue(pMyDevice,
VL53L0_CHECKENABLE_SIGNAL_RATE_FINAL_RANGE,
(FixPoint1616_t)(0.25*65536));
}
if (Status == VL53L0_ERROR_NONE) {
Status = VL53L0_SetLimitCheckValue(pMyDevice,
VL53L0_CHECKENABLE_SIGMA_FINAL_RANGE,
(FixPoint1616_t)(32*65536));
}
if (Status == VL53L0_ERROR_NONE) {
Status = VL53L0_SetMeasurementTimingBudgetMicroSeconds(pMyDevice,
20000);
}
九、代碼示例
完整工程在這兒:簡單的測距。
依照上面介紹的思想,我們專門寫一個校準函數,完成4個校準,然後把校準數據保存到程序中的常量中,以後直接賦值即可。
校準函數:
uint8_t Debug_Ref=0;
uint32_t refSpadCount;
uint8_t isApertureSpads;
uint8_t VhvSettings;
uint8_t PhaseCal;
int32_t OffsetMicroMeter;
uint32_t XTalkCompensation;
VL53L0X_Error VL53L0x_Ref(VL53L0X_Dev_t *dev) //校準函數
{
VL53L0X_Error Status = VL53L0X_ERROR_NONE;
dev->I2cDevAddr = 0x52; //I2C地址(上電默認0x52)
dev->comms_type = 1; //I2C通信模式
dev->comms_speed_khz = 400; //I2C通信速率
VL53l0x_GPIO_Init();
VL53L0X_IIC_Init();
XSHUT_LOW;
delay_ms(30);
XSHUT_HIGH; //拉高XSHUT引腳
delay_ms(30);
Status = VL53L0X_DataInit(dev);
if(Status!=VL53L0X_ERROR_NONE) goto MyError;
Status = VL53L0X_StaticInit(dev);
if(Status!=VL53L0X_ERROR_NONE) goto MyError;
Status=VL53L0X_PerformRefSpadManagement(dev, &refSpadCount, &isApertureSpads); //參考SPADs校準
if(Status!=VL53L0X_ERROR_NONE) goto MyError;
Status=VL53L0X_PerformRefCalibration(dev, &VhvSettings, &PhaseCal); //溫度校準
if(Status!=VL53L0X_ERROR_NONE) goto MyError;
while(1){ if(Debug_Ref==1) { Debug_Ref=0; break; } } //等待校準條件準備好
Status=VL53L0X_PerformOffsetCalibration(dev,100<<16,&OffsetMicroMeter); //偏移校準,把白色物體放在100mm處進行
if(Status!=VL53L0X_ERROR_NONE) goto MyError;
while(1){ if(Debug_Ref==1) { Debug_Ref=0; break; } } //等待校準條件準備好
Status=VL53L0X_PerformXTalkCalibration(dev,450<<16,&XTalkCompensation); //蓋玻片校準,把灰色物體放在校準距離處進行
if(Status!=VL53L0X_ERROR_NONE) goto MyError;
MyError:
while(1);
}
校準後把校準函數註釋掉,使用下面函數來直接初始化即可:
const uint32_t refSpadCount=4;
const uint8_t isApertureSpads=0;
const uint8_t VhvSettings=26;
const uint8_t PhaseCal=1;
const int32_t OffsetMicroMeter=15000;
const uint32_t XTalkCompensation=99;
VL53L0X_Error VL53L0x_Init(VL53L0X_Dev_t *dev)
{
VL53L0X_Error Status = VL53L0X_ERROR_NONE;
dev->I2cDevAddr = 0x52; //I2C地址(上電默認0x52)
dev->comms_type = 1; //I2C通信模式
dev->comms_speed_khz = 400; //I2C通信速率
VL53l0x_GPIO_Init();
VL53L0X_IIC_Init();
XSHUT_LOW;
delay_ms(30);
XSHUT_HIGH;
delay_ms(30);
Status = VL53L0X_DataInit(dev);
if(Status!=VL53L0X_ERROR_NONE) { return Status; }
Status = VL53L0X_StaticInit(dev);
if(Status!=VL53L0X_ERROR_NONE) { return Status; }
Status = VL53L0X_SetReferenceSpads(dev,refSpadCount,isApertureSpads); //設定參考SPADs的校準值
if(Status!=VL53L0X_ERROR_NONE) { return Status; }
Status=VL53L0X_SetRefCalibration(dev,VhvSettings,PhaseCal); //設定溫度校準值
if(Status!=VL53L0X_ERROR_NONE) { return Status; }
Status=VL53L0X_SetOffsetCalibrationDataMicroMeter(dev,OffsetMicroMeter);//設定偏移校準值
if(Status!=VL53L0X_ERROR_NONE) { return Status; }
Status=VL53L0X_SetXTalkCompensationRateMegaCps(dev,XTalkCompensation); //設定蓋玻片校準值
if(Status!=VL53L0X_ERROR_NONE) { return Status; }
Status = VL53L0X_SetXTalkCompensationEnable(dev,1);
if(Status!=VL53L0X_ERROR_NONE) { return Status; }
Status = VL53L0X_SetDeviceMode(dev,VL53L0X_DEVICEMODE_CONTINUOUS_RANGING);
if(Status!=VL53L0X_ERROR_NONE) { return Status; }
Status = VL53L0X_StartMeasurement(dev);
if(Status!=VL53L0X_ERROR_NONE) { return Status; }
return Status;
}
上面是 默認模式的測距配置(range profiles),如果要使用長距離測距配置,初始化函數爲:
VL53L0X_Error VL53L0x_Init(VL53L0X_Dev_t *dev)
{
VL53L0X_Error Status = VL53L0X_ERROR_NONE;
dev->I2cDevAddr = 0x52; //I2C地址(上電默認0x52)
dev->comms_type = 1; //I2C通信模式
dev->comms_speed_khz = 400; //I2C通信速率
VL53l0x_GPIO_Init();
VL53L0X_IIC_Init();
XSHUT_LOW;
delay_ms(30);
XSHUT_HIGH;
delay_ms(30);
Status = VL53L0X_DataInit(dev);
if(Status!=VL53L0X_ERROR_NONE) { return Status; }
Status = VL53L0X_StaticInit(dev);
if(Status!=VL53L0X_ERROR_NONE) { return Status; }
Status = VL53L0X_SetReferenceSpads(dev,refSpadCount,isApertureSpads); //設定參考SPADs的校準值
if(Status!=VL53L0X_ERROR_NONE) { return Status; }
Status=VL53L0X_SetRefCalibration(dev,VhvSettings,PhaseCal); //設定溫度校準值
if(Status!=VL53L0X_ERROR_NONE) { return Status; }
Status=VL53L0X_SetOffsetCalibrationDataMicroMeter(dev,OffsetMicroMeter);//設定偏移校準值
if(Status!=VL53L0X_ERROR_NONE) { return Status; }
Status=VL53L0X_SetXTalkCompensationRateMegaCps(dev,XTalkCompensation); //設定蓋玻片校準值
if(Status!=VL53L0X_ERROR_NONE) { return Status; }
Status = VL53L0X_SetXTalkCompensationEnable(dev,1); //使能蓋玻片矯正
if(Status!=VL53L0X_ERROR_NONE) { return Status; }
Status = VL53L0X_SetDeviceMode(dev,VL53L0X_DEVICEMODE_CONTINUOUS_RANGING); //連續測量模式
if(Status!=VL53L0X_ERROR_NONE) { return Status; }
Status = VL53L0X_SetLimitCheckValue(dev,VL53L0X_CHECKENABLE_SIGNAL_RATE_FINAL_RANGE,(FixPoint1616_t)(0.1*65536)); //返回信號大小的測量值
if(Status!=VL53L0X_ERROR_NONE) { return Status; }
Status = VL53L0X_SetLimitCheckValue(dev,VL53L0X_CHECKENABLE_SIGMA_FINAL_RANGE,(FixPoint1616_t)(60*65536)); //參考和返回SPAD陣列之間的時差
if(Status!=VL53L0X_ERROR_NONE) { return Status; }
Status =VL53L0X_SetMeasurementTimingBudgetMicroSeconds(dev,33000);
if(Status!=VL53L0X_ERROR_NONE) { return Status; }
Status = VL53L0X_SetVcselPulsePeriod(dev,VL53L0X_VCSEL_PERIOD_PRE_RANGE, 18); //設定VCSEL脈衝週期
if(Status!=VL53L0X_ERROR_NONE) { return Status; }
Status = VL53L0X_SetVcselPulsePeriod(dev,VL53L0X_VCSEL_PERIOD_FINAL_RANGE, 14); //設定VCSEL脈衝週期範圍
if(Status!=VL53L0X_ERROR_NONE) { return Status; }
Status = VL53L0X_StartMeasurement(dev); //開始測量
if(Status!=VL53L0X_ERROR_NONE) { return Status; }
return Status;
}
每次讀取測量數據後,都要清除中斷,不然中斷引腳不會更新:
其他的一些初始化函數:
VL53L0X_SetLimitCheckEnable(dev,VL53L0X_CHECKENABLE_SIGMA_FINAL_RANGE,1); //開啓SIGMA範圍檢查
//SIGMA表示是激光飛行時間
VL53L0X_SetLimitCheckEnable(dev,VL53L0X_CHECKENABLE_SIGNAL_RATE_FINAL_RANGE,1); //使能信號速率範圍檢查
VL53L0X_SetLimitCheckValue(dev,VL53L0X_CHECKENABLE_SIGMA_FINAL_RANGE,sigmaLimit);//設定SIGMA範圍
VL53L0X_SetLimitCheckValue(dev,VL53L0X_CHECKENABLE_SIGNAL_RATE_FINAL_RANGE,signalLimit);//設定信號速率範圍範圍
VL53L0X_SetMeasurementTimingBudgetMicroSeconds(dev,timingBudget);//設定完整測距最長時間
VL53L0X_SetVcselPulsePeriod(dev, VL53L0X_VCSEL_PERIOD_PRE_RANGE, preRangeVcselPeriod);//設定VCSEL脈衝週期
VL53L0X_SetVcselPulsePeriod(dev, VL53L0X_VCSEL_PERIOD_FINAL_RANGE, finalRangeVcselPeriod);//設定VCSEL脈衝週期範圍
//sigmaLimit, signalLimit, timingBudget, preRangeVcselPeriod, finalRangeVcselPeriod參數的典型值可以在API手冊第7部分 Example API range profiles 找到