Hi3061M——VL53L0X激光测距(IIC)(同样适用于其他MCU)1
目录
前言
手头正好有一个空闲的激光测距模块VL53L0X,想在Hi3061M上跑一下测距,以前并没有用过VL53L0X,想着以为还是向以前一样IIC读写寄存器就可以后面发现这个芯片是提供API的,调用API的方式来使用,在网上搜了搜资料,看着感觉好像很麻烦有好多的文件要导入,好像还要移植什么什么的,长长的API英文文档还要看,当时就想放弃了,后面认真弄了下,找了些资料其实也没那么难的。
本文主要言简意赅的介绍和实现VL53L0X的使用。
VL53L0X的简介
激光测距模块,支持电压1.6-3.5V,IIC通讯,最大速率400KHz,测量范围3cm~200cm,最快测量一次时间为20ms(准确度会受影响),33ms默认时间,测量时间加倍标准误差越小。
主要IO介绍:
引脚 | 功能 |
---|---|
SCL | IIC时钟线 |
SDA | IIC数据线 |
XSHUT | shutdown,高电平工作,低电平摆烂 |
GPIO1 | 中断线,测量完毕触发中断,可读取测量结果 |
工作过程: 940nm(频段的激光为不可见光,且不危害人眼)无红光闪烁激光器,发射出激光,反射回程的IR光通过高灵敏的领先**SPAD(单光子雪崩二极管)**阵列进行测量。
VL53L0X 传感器还配有内置物理红外滤光片,可增大测量距离、较少环境光的干扰,以及对提高玻璃罩光学串扰的抗扰度。当然具体的工作实现我是不知道的啦,这个正常情况我们使用也无需知道。
此外测量模式有三种:
连续定时测量的意思就是测量后延迟固定时间在进行重复测量。
四种测量精度:
测量耗时 | 最远测量距离 | 典型应用 | |
---|---|---|---|
默认模式 | 30ms | 1.2m(白色目标) | 标准 |
高精度 | 200ms | 1.2m(白色目标) | 精准测量 |
远距离 | 33ms | 2m(白色目标) | 黑暗条件下的远距离测量 |
高速度 | 20ms | 1.2m(白色目标) | 优先速度,准确率有所下降 |
不同工作模式会有多加几行代码实现,通过调用相应的API实现,对应代码可以查阅API手册,在第7.2节有具体的代码实现。
一般工作在默认模式下即可,本文选择在默认模式下,此外可以通过设置单次测量时间来提高准确率。
VL53L0X的初始化和效准过程的相关API
API文档中主要讲的就是效准的过程。
总体可以分为6个部分。
API中第二章和第四章都提到了这6个部分,第二章似乎是制造生产的效准过程,而第四章是系统初始化过程。详见文档。
简单介绍一下6个部分,感觉不是高精度高要求的使用,不需要多了解。
1、设备初始化
VL53L0X_DataInit(VL53L0X_DEV dev);
2、数据初始化
允许加载特定用途的设备配置
VL53L0X_StaticInit(VL53L0X_DEV dev);
3、SPADs(单光子雪崩二极管阵列)校准
VL53L0X可能覆盖玻璃,所以需要用户自行效准,效准一次即可,数据可以进行存储。
VL53L0X_Error VL53L0X_PerformRefSpadManagement(VL53L0X_DEV Dev, uint32_t *refSpadCount,
uint8_t *isApertureSpads)
这个效准不需要特定的环境,这个API最后会输出的数字和类型会设置到设备中,可以对两个值进行存储,下次就不用在效准。
**注意:**如果设备覆盖了高反射性的物体,可能会出错,错误码为-50,这个时候需要移除物体重新效准。
4、温度效准
这个没什么好说的,校准与温度的敏感度有关的两个参数VHV 和phase cal,具体是什么我没研究。
VL53L0X_Error VL53L0X_PerformRefCalibration(VL53L0X_DEV Dev, uint8_t *pVhvSettings, uint8_t *pPhaseCal)
5、偏移校准
这个生产时校准过存在device NVM中,但是还是产品可能覆盖玻璃,导致这个值不准,需要重新效准。
这个需要自己手动校准的,但是这个意思就是误差校准,比如测量时和实际的结果存在多少mm的误差,±这个误差,大概就是这么回事,如果真想可以自己通过程序校准也一样的。
VL53L0X_Error VL53L0X_PerformOffsetCalibration(VL53L0X_DEV Dev, FixPoint1616_t CalDistanceMilliMeter, int32_t *pOffsetMicroMeter)
6、串扰校准
这个受到的影响也是,设备覆盖了玻璃,会影响测量性能,具体影响程度取决于玻璃。
这个效准似乎还分两种情况近距离和远距离,近距离有一个线性区,远距离有个曲线区域。具体大家可以查看API有相关介绍。
VL53L0X_Error VL53L0X_PerformXTalkCalibration(VL53L0X_DEV Dev,FixPoint1616_t XTalkCalDistance,
FixPoint1616_t *pXTalkCompensationRateMegaCps)
值得一提的是,这个第5、6步可以忽略,可能生产出来的时候就效准过的吧,也可能我这个模块没有覆盖玻璃,总之没有执行这两步骤可以正常初始化并使用,毕竟只是校准过程而已。
此外还有两个参数需要配置:测量模式和测量时间。
这个前面提到了这里不多介绍,相关API如下
/**设置工作模式
* 有三种工作模式
* 单次测量 0
* 连续测量 1
* 连续延时测量 3
*/
VL53L0X_SetDeviceMode(dev, DEMO_DEVICE_MODE);
/**设置测量时间
* 默认时间是33ms,最小的时间是20ms,注意这里参数是us,即设置20ms,需设置20*1000
* 这个会涉及到准确率,太快,准确率会有所下降
* 增加测量时间会提高准确率
* 测量时间增加到2倍,测量的标准差减少到根号2
*/
VL53L0X_SetMeasurementTimingBudgetMicroSeconds(dev, DEMO_BUDGET_TIME);
VL53L0X开始测量和获取测量值
不再过多介绍,就是调用API
// 开始测量
void vl53l0x_start(void){
/*开始测量*/
VL53L0X_StartMeasurement(&demo_dev);
DBG_PRINTF("VL53L0X start ranging!\r\n");
}
// 停止测量---连续测量模式下
void vl53l0x_stop(void){
/*开始测量*/
VL53L0X_StopMeasurement(&demo_dev);
DBG_PRINTF("VL53L0X stop ranging!\r\n");
}
// 获取测量结果 单位mm
unsigned short get_distance(void){
uint8_t ret;
VL53L0X_RangingMeasurementData_t data;
/*检查是否完成一次测量*/
do {
VL53L0X_GetMeasurementDataReady(&demo_dev, &ret);
} while (ret != 1);
/*获取测量结果*/
VL53L0X_GetRangingMeasurementData(&demo_dev, &data);
return data.RangeMilliMeter;
}
VL53L0X移植配置
VL53L0X的移植配置主要就是讲API文档中的文件放入工程中,用到的保留下,不用的删除,此外需要讲IIC的接口函数进行置换即可,换位MCU所使用的IIC。
具体需要包含的文件结构:
其中core是没有动过的,platform删掉了一些文件,主要需要修改的文件就**“vl53l0x_i2c_platform.c”**,可以直接复制进去,需要修改就这文件中对应的函数。
mine中是自己根据mcu实现的IIC相关代码,引脚初始化和通讯过程什么的。
#include "clock.h"
#include "debug.h"
#include <stdio.h> // sprintf(), vsnprintf(), printf()
#ifdef _MSC_VER
#define snprintf _snprintf
#endif
#include "vl53l0x_i2c_platform.h"
#include "vl53l0x_def.h"
#include "vl53l0x_platform_log.h"
#ifdef VL53L0X_LOG_ENABLE
#define trace_print(level, ...) trace_print_module_function(TRACE_MODULE_PLATFORM, level, TRACE_FUNCTION_NONE, ##__VA_ARGS__)
#define trace_i2c(...) trace_print_module_function(TRACE_MODULE_NONE, TRACE_LEVEL_NONE, TRACE_FUNCTION_I2C, ##__VA_ARGS__)
#endif
char debug_string[VL53L0X_MAX_STRING_LENGTH_PLT];
#define MIN_COMMS_VERSION_MAJOR 1
#define MIN_COMMS_VERSION_MINOR 8
#define MIN_COMMS_VERSION_BUILD 1
#define MIN_COMMS_VERSION_REVISION 0
#define STATUS_OK 0x00
#define STATUS_FAIL 0x01
int32_t VL53L0X_write_multi(uint8_t address, uint8_t index, uint8_t *pdata, int32_t count)
{
int32_t status = STATUS_OK;
int32_t i;
vl53l0x_iic_start();
vl53l0x_iic_send_byte((address << 1) | 0);
if (vl53l0x_iic_wait_ack() == 1)
{
vl53l0x_iic_stop();
return STATUS_FAIL;
}
vl53l0x_iic_send_byte(index);
if (vl53l0x_iic_wait_ack() == 1)
{
vl53l0x_iic_stop();
return STATUS_FAIL;
}
for (i=0; i<count; i++)
{
vl53l0x_iic_send_byte(pdata[i]);
if (vl53l0x_iic_wait_ack() == 1)
{
vl53l0x_iic_stop();
return STATUS_FAIL;
}
}
vl53l0x_iic_stop();
return status;
}
int32_t VL53L0X_read_multi(uint8_t address, uint8_t index, uint8_t *pdata, int32_t count)
{
int32_t status = STATUS_OK;
vl53l0x_iic_start();
vl53l0x_iic_send_byte((address << 1) | 0);
if (vl53l0x_iic_wait_ack() == 1)
{
vl53l0x_iic_stop();
return STATUS_FAIL;
}
vl53l0x_iic_send_byte(index);
if (vl53l0x_iic_wait_ack() == 1)
{
vl53l0x_iic_stop();
return STATUS_FAIL;
}
vl53l0x_iic_stop();
vl53l0x_iic_start();
vl53l0x_iic_send_byte((address << 1) | 1);
if (vl53l0x_iic_wait_ack() == 1)
{
vl53l0x_iic_stop();
return STATUS_FAIL;
}
while (count)
{
*pdata = vl53l0x_iic_read_byte((count > 1) ? 1 : 0);
count--;
pdata++;
}
vl53l0x_iic_stop();
return status;
}
int32_t VL53L0X_write_byte(uint8_t address, uint8_t index, uint8_t data)
{
int32_t status = STATUS_OK;
const int32_t cbyte_count = 1;
status = VL53L0X_write_multi(address, index, &data, cbyte_count);
return status;
}
int32_t VL53L0X_write_word(uint8_t address, uint8_t index, uint16_t data)
{
int32_t status = STATUS_OK;
uint8_t buffer[BYTES_PER_WORD];
// Split 16-bit word into MS and LS uint8_t
buffer[0] = (uint8_t)(data >> 8);
buffer[1] = (uint8_t)(data & 0x00FF);
status = VL53L0X_write_multi(address, index, buffer, BYTES_PER_WORD);
return status;
}
int32_t VL53L0X_write_dword(uint8_t address, uint8_t index, uint32_t data)
{
int32_t status = STATUS_OK;
uint8_t buffer[BYTES_PER_DWORD];
// Split 32-bit word into MS ... LS bytes
buffer[0] = (uint8_t) (data >> 24);
buffer[1] = (uint8_t)((data & 0x00FF0000) >> 16);
buffer[2] = (uint8_t)((data & 0x0000FF00) >> 8);
buffer[3] = (uint8_t) (data & 0x000000FF);
status = VL53L0X_write_multi(address, index, buffer, BYTES_PER_DWORD);
return status;
}
int32_t VL53L0X_read_byte(uint8_t address, uint8_t index, uint8_t *pdata)
{
int32_t status = STATUS_OK;
int32_t cbyte_count = 1;
status = VL53L0X_read_multi(address, index, pdata, cbyte_count);
return status;
}
int32_t VL53L0X_read_word(uint8_t address, uint8_t index, uint16_t *pdata)
{
int32_t status = STATUS_OK;
uint8_t buffer[BYTES_PER_WORD] = {0};
status = VL53L0X_read_multi(address, index, buffer, BYTES_PER_WORD);
// DBG_PRINTF("status: %d\r\n",status);
*pdata = ((uint16_t)buffer[0]<<8) + (uint16_t)buffer[1];
return status;
}
int32_t VL53L0X_read_dword(uint8_t address, uint8_t index, uint32_t *pdata)
{
int32_t status = STATUS_OK;
uint8_t buffer[BYTES_PER_DWORD];
status = VL53L0X_read_multi(address, index, buffer, BYTES_PER_DWORD);
*pdata = ((uint32_t)buffer[0]<<24) + ((uint32_t)buffer[1]<<16) + ((uint32_t)buffer[2]<<8) + (uint32_t)buffer[3];
return status;
}
int32_t VL53L0X_platform_wait_us(int32_t wait_us)
{
int32_t status = STATUS_OK;
float wait_ms = (float)wait_us/1000.0f;
BASE_FUNC_DELAY_US(wait_us);
#ifdef VL53L0X_LOG_ENABLE
trace_i2c("Wait us : %6d\n", wait_us);
#endif
return status;
}
int32_t VL53L0X_wait_ms(int32_t wait_ms)
{
int32_t status = STATUS_OK;
BASE_FUNC_DELAY_MS(wait_ms);
#ifdef VL53L0X_LOG_ENABLE
trace_i2c("Wait ms : %6d\n", wait_ms);
#endif
return status;
}
结语
太晚了,先下班了,具体的移植和实现,相关的代码讲解啥的明天再更,下班了下班了,感谢支持。
作者:NS_ice