激光测距传感器TOFSense UART模式的使用
随笔记-获取TOFSense的数据
以下所有代码适用与TOFSense P/PS/F/FP 不适用于M系列需要小改一下
TOFSense
TOFSense是Nooploop深圳空循环的一款激光测距传感器,前面的黑框就是激光发射与接收的地方。模块激光发射视场角有27°,也就是说实际上模块打出去的激光覆盖范围是一个顶点在黑框上的圆锥形状,参考下图,打出的是范围激光但只输出一个值,看模块手册说内部通过多次测量根据数据大小占比来输出占比高的数据,基于这种机制作为避障用就很优秀1~3个模块就可以覆盖小车前方,本文将基于stm32f1介绍它的数据接收处理问题。
本文代码Git地址
UART模式
1、主动输出模式
TOF这个模块可以通过UART与CAN进行通信,这里先介绍UART模式,想要获取数据需要先看一下模块发送数据包的组成以及波特率等问题,通过产品手册可以了解到TOF出厂波特率是921600,这个注意一下单片机波特率要一致,或者可以通过他们的上位机NAssistant使用usb转ttl更改TOF模块的波特率,数据协议如下,还是很好理解的,前两位固定为0x57 0x00然后id、测距距离(dis)、距离状态指示(dis_status)、信号强度(signal_strength)校验和,知道协议具体组成以及对应位后就可以进行编程了。
官方有提供协议解析包,将下面红框中的文件添加到自己的工程中,不会就百度如何添加.c .h文件以及文件路径到工程中
然后看一下解析包做了什么,主要看nlink_tofsense_frame0.c/.h文件就可以,其他几个文件就是一些通用数据处理转换以及头文件,nlink_tofsense_frame0.h里面声明了一个结构体g_nts_frame0,结构体内有一个函数指针,结构体中的result就是解析后的保存数据用的结构体,进入nlink_tofsense_frame0.c中,可以看到函数指针指向uint8_t UnpackData(const uint8_t *data, size_t data_length),那么它就是解析函数了,从参数看我们需要给它传数据以及数据大小。OK 至此我们已经知道了解析包中干了什么,既然知道了需要传入数据那么我们只需要获取到数据然后传入即可。获取数据的步骤就是先用串口接数据,然后调用解析包中的解析函数将数据传入即可正常获取解析数据。
//nlink_tofsense_frame0.h
...省略...
typedef struct
{
uint8_t id;
uint32_t system_time;
float dis;
uint8_t dis_status;
uint16_t signal_strength;
uint8_t range_precision;//cm, only valid in tofsense-f
} nts_frame0_result_t;
typedef struct
{
const size_t fixed_part_size;
const uint8_t frame_header;
const uint8_t function_mark;
nts_frame0_result_t result;
uint8_t (*const UnpackData)(const uint8_t *data, size_t data_length);
} nts_frame0_t;
extern nts_frame0_t g_nts_frame0;
...省略...
//nlink_tofsense_frame0.c
...省略...
static uint8_t UnpackData(const uint8_t *data, size_t data_length)
{
if (data_length < g_nts_frame0.fixed_part_size ||
data[0] != g_nts_frame0.frame_header ||
data[1] != g_nts_frame0.function_mark)
return 0;
if (!NLINK_VerifyCheckSum(data, g_nts_frame0.fixed_part_size))
return 0;
memcpy(&g_frame, data, g_nts_frame0.fixed_part_size);
g_nts_frame0.result.id = g_frame.id;
g_nts_frame0.result.system_time = g_frame.system_time;
g_nts_frame0.result.dis_status = g_frame.dis_status;
g_nts_frame0.result.signal_strength = g_frame.signal_strength;
g_nts_frame0.result.range_precision = g_frame.range_precision;
g_nts_frame0.result.dis = NLINK_ParseInt24(g_frame.dis) / 1000.0f;
return 1;
}
...省略...
至于接收数据则有很多种方法了,根据之前了解到的固定帧头0x57来判断接收或者使用官方例程中通过UART的空闲中断以及DMA接数据的方法都是可以的
1)、一个字节一个字节的接收
通过帧头判断接收,需要重写回调函数并在其中进行数据处理
main.c
/* USER CODE BEGIN 0 */
#define TOF_UART huart1
uint8_t rx_temp; //临时接收缓存
uint8_t u_rx_buf[16]; //缓存数组
float final_data = 0; //最终输出
//串口数据处理、解析
#include "nlink_tofsense_frame0.h"
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
static uint8_t c = 0;
static float old_data = 0;
if (c == 0){
if (rx_temp == 0x57){ //判断帧头
u_rx_buf[c++] = rx_temp;
}
else{
c = 0;
}
}
else if (c == 1){
if (rx_temp == 0x00){ //判断关键字
u_rx_buf[c++] = rx_temp;
}
else{
c = 0;
}
}
else {
u_rx_buf[c++] = rx_temp;
if (c >= 16){ //数据接够了 开始解析
if (g_nts_frame0.UnpackData(u_rx_buf, sizeof(u_rx_buf)/sizeof(u_rx_buf[0]))){
//这里做了一个简单的过滤与数据处理
//不同型号的TOFsense过滤参数是不一样的需要根据数据手册来处理
if (
g_nts_frame0.result.dis <= 0 || //量程过滤
g_nts_frame0.result.dis >= 5 ||
g_nts_frame0.result.dis_status == 14 || //距离状态指示过滤
g_nts_frame0.result.dis_status == 255 ||
g_nts_frame0.result.signal_strength == 0 //信号强度过滤
){
final_data = old_data; //对异常数据取上一正常数据的处理
}
else{
final_data = g_nts_frame0.result.dis; //更新当前数据
old_data = g_nts_frame0.result.dis; //更新上一数据
}
printf("接收的数据为:%f\r\n",final_data); //打印到串口
}
c = 0; //清空索引
}
}
HAL_UART_Receive_IT(&TOF_UART, &rx_temp, 1);
}
/* USER CODE END 0 */
int main(void)
{
...
/* USER CODE BEGIN 2 */
HAL_UART_Receive_IT(&huart1, &rx_temp, 1); //开启接收
/* USER CODE END 2 */
...
while (1)
{
}
}
2)、通过DMA+空闲中断一起接
是TOF官网提供例程的接收方式,通过空闲中断以及DMA进行接收,在中断服务函数中处理,这里只做了简单的移植并没有进行数据处理~~
# main.c中
/* USER CODE BEGIN 0 */
uint8_t u_rx_buf[16];
/* USER CODE END 0 */
int main(void)
{
...
/* USER CODE BEGIN 2 */
__HAL_UART_ENABLE_IT(&huart1, UART_IT_IDLE); // 启用串口空闲中断
HAL_UART_Receive_DMA(&huart1, u_rx_buf, sizeof(u_rx_buf)); // 开启DMA接收
/* USER CODE END 2 */
...
while (1)
{
}
}
# 中断处理文件stm32f1xx_it.c中
/**
* @brief This function handles USART1 global interrupt.
*/
void USART1_IRQHandler(void)
{
/* USER CODE BEGIN USART1_IRQn 0 */
/* USER CODE END USART1_IRQn 0 */
HAL_UART_IRQHandler(&huart1);
/* USER CODE BEGIN USART1_IRQn 1 */
#include "nlink_tofsense_frame0.h"
if (__HAL_UART_GET_FLAG(&huart1, UART_FLAG_IDLE) != RESET) // 确认是否是空闲中断
{
uint32_t TOF_datalen;
__HAL_UART_CLEAR_IDLEFLAG(&huart1); // 清除串口空闲中断标志位
HAL_UART_AbortReceive(&huart1); // 关闭DMA传输
TOF_datalen = sizeof(u_rx_buf) - __HAL_DMA_GET_COUNTER(&hdma_usart1_rx); // 计算接收数据长度
if (g_nts_frame0.UnpackData(u_rx_buf, TOF_datalen)) // 解析协议数据
{
printf("接收的数据为:%f\r\n",g_nts_frame0.result.dis);
}
memset(&u_rx_buf, 0, sizeof(u_rx_buf));
HAL_UART_Receive_DMA(&huart1, u_rx_buf, sizeof(u_rx_buf)); // 开启DMA接收
}
/* USER CODE END USART1_IRQn 1 */
}
运行结果
2、查询输出模式
TOF支持查询输出你问它答你不问就不答,与主动输出模式唯一的区别就是想获取数据之前先给模块发一下查询命令,基于上述任意一种获取数据代码的基础上加上查询代码即可,先看一下手册的查询协议,如下,除id与校验和外都是固定的参数
//发送查询命令获取数据,在需要时调用即可
//PS:相同ID每两次查询之间最好延迟一段时间,一般为 delay = 1 / 模块刷新频率,如果超过这个速度会得到重复的数据
void Inquire_data(uint8_t id)
{
static uint8_t u_tx_buf[8];
u_tx_buf[0] = 0x57; //帧头
u_tx_buf[1] = 0x10; //关键字
u_tx_buf[2] = 0xFF;
u_tx_buf[3] = 0xFF;
u_tx_buf[4] = id; //所需查询模块的ID
u_tx_buf[5] = 0xFF;
u_tx_buf[6] = 0xFF;
u_tx_buf[7] = 0; //清空校验和
for (int i = 0; i < 7; i++)
{
u_tx_buf[7] += u_tx_buf[i];
}
HAL_UART_Transmit_DMA(&TOF_UART, u_tx_buf, sizeof(u_tx_buf)); //发送查询命令
}
3、级联
。。。没啥写的,单个数据都获取了,for循环调用查询函数就行 别问,问就是懒
OK,TOFSense模块的UART数据获取就已经完成了,如果有什么问题欢迎留言指正