LD14雷达STM32F103C8T6获取LD14激光雷达数据

本文介绍了如何使用STM32F103C8T6微控制器通过UART接口接收并解析LD14激光雷达的数据。LD14雷达采用三角测距法,支持内部和外部调速,并通过UART单向发送测量数据包。数据包包括起始符、帧类型、转速、角度、距离、时间戳和CRC校验等信息。STM32的配置涉及RCC时钟、USART1和USART3中断、GPIO及串口接收中断处理函数,用于实时接收和处理雷达数据。
摘要由CSDN通过智能技术生成

LD14雷达STM32F103C8T6获取LD14激光雷达数据

LD14介绍

LD14 主要由激光测距核心,无线传电单元,无线通讯单元,角度测量单元、电机驱动单元和机械外壳组成。
LD14 测距核心采用三角测量法技术,可进行每秒 2300 次的测距。每次测距时,LD14 从一个固定的角度发射出红外激光,激光遇到目标物体后被反射到接收单元。通过激光、目标物体、接收单元形成的三角关系,从而解算出距离。
获取到距离数据后,LD14 会融合角度测量单元测量到的角度值组成点云数据,然后通过无线通讯将点云数据发送到外部接口。同时电机驱动单元会驱动电机,通过 PID 算法闭环控制到指定的转速。
LD14 点云数据形成的环境扫描图意图如下:

在这里插入图片描述

LD14通讯接口

	LD14 使用 1.25mm 4PIN 连接器与外部系统连接,实现供电和数据接收,具

体接口定义和参数要求见下图/表:
在这里插入图片描述

LD14 支持内部控速和外部控速。在 PWM 引脚接地或悬空时,默认为内部调速,默认转速为 6Hz。外部控速需要在 PWM 引脚接入方波信号,可通过 PWM信号占空比控制电机的启、停和转速。触发外部控速的条件:a、输入 PWM 频率 15-30K,推荐 24K;b、占空比在(45%, 55%)区间内(不包含 45%和 55%),且最少 100ms 持续输入时间。触发外部控速后就一直处于外部控速状态,除非断电重启才会恢复内部控速;同时可以通过调节 PWM 占空比进行转速控制。由于每个产品电机的个体差异,占空比设置为典型值时实际转速可能会有差异,如要精确控制电机转速,需根据接收数据中的转速信息进行闭环控制。注:不使用外部控速时,必须将 PWM 引脚接地或悬空。
LD14 的数据通讯采用标准异步串口(UART)单向发送,其传输参数如下表所
示:
在这里插入图片描述

通讯协议

一、数据包格式
LD14 采用单向通讯,稳定工作后,即开始发送测量数据包,不需要发送任
何指令。测量数据包格式如下图所示。
在这里插入图片描述
⚫ **起始符:**长度 1 Byte,值固定为 0x54,表示数据包的开始;
⚫ **VerLen:**长度 1 Byte,高三位表示帧类型,目前固定为 1,低五位表示
一个包的测量点数,目前固定为 12,故该字节值固定为 0x2C;
雷达转速:长度 2 Byte,单位为度每秒;
起始角度: 长度 2 Byte,单位为 0.01 度;
⚫ **数据:**一个测量数据长度为 3 个字节,详细解析请见下一小节;
结束角度:长度 2 Byte,单位为 0.01 度;
⚫ **时间戳:**长度 2 Byte,单位为 ms,最大为 30000,到达 30000 会重新
计数;
⚫ **CRC 校验:**前面所有数据的校验和;
数据结构参考如下:

#define POINT_PER_PACK 12
COPYRIGHT©2017-2022 SHENZHEN LDROBOT CO., LTD. 6
#define HEADER 0x54
typedef struct __attribute__((packed)) {
uint16_t distance;
uint8_t intensity;
} LidarPointStructDef;
typedef struct __attribute__((packed)) {
uint8_t header;
uint8_t ver_len;
uint16_t speed;
uint16_t start_angle;
LidarPointStructDef point[POINT_PER_PACK];
uint16_t end_angle;
uint16_t timestamp;
uint8_t crc8;
}LiDARFrameTypeDef;
//CRC 校验计算方式如下:
static const uint8_t CrcTable[256] ={
0x00, 0x4d, 0x9a, 0xd7, 0x79, 0x34, 0xe3,
0xae, 0xf2, 0xbf, 0x68, 0x25, 0x8b, 0xc6, 0x11, 0x5c, 0xa9, 0xe4, 0x33,
0x7e, 0xd0, 0x9d, 0x4a, 0x07, 0x5b, 0x16, 0xc1, 0x8c, 0x22, 0x6f, 0xb8,
0xf5, 0x1f, 0x52, 0x85, 0xc8, 0x66, 0x2b, 0xfc, 0xb1, 0xed, 0xa0, 0x77,
0x3a, 0x94, 0xd9, 0x0e, 0x43, 0xb6, 0xfb, 0x2c, 0x61, 0xcf, 0x82, 0x55,
0x18, 0x44, 0x09, 0xde, 0x93, 0x3d, 0x70, 0xa7, 0xea, 0x3e, 0x73, 0xa4,
0xe9, 0x47, 0x0a, 0xdd, 0x90, 0xcc, 0x81, 0x56, 0x1b, 0xb5, 0xf8, 0x2f,
0x62, 0x97, 0xda, 0x0d, 0x40, 0xee, 0xa3, 0x74, 0x39, 0x65, 0x28, 0xff,
0xb2, 0x1c, 0x51, 0x86, 0xcb, 0x21, 0x6c, 0xbb, 0xf6, 0x58, 0x15, 0xc2,
0x8f, 0xd3, 0x9e, 0x49, 0x04, 0xaa, 0xe7, 0x30, 0x7d, 0x88, 0xc5, 0x12,
0x5f, 0xf1, 0xbc, 0x6b, 0x26, 0x7a, 0x37, 0xe0, 0xad, 0x03, 0x4e, 0x99,
0xd4, 0x7c, 0x31, 0xe6, 0xab, 0x05, 0x48, 0x9f, 0xd2, 0x8e, 0xc3, 0x14,
0x59, 0xf7, 0xba, 0x6d, 0x20, 0xd5, 0x98, 0x4f, 0x02, 0xac, 0xe1, 0x36,
0x7b, 0x27, 0x6a, 0xbd, 0xf0, 0x5e, 0x13, 0xc4, 0x89, 0x63, 0x2e, 0xf9,
0xb4, 0x1a, 0x57, 0x80, 0xcd, 0x91, 0xdc, 0x0b, 0x46, 0xe8, 0xa5, 0x72,
0x3f, 0xca, 0x87, 0x50, 0x1d, 0xb3, 0xfe, 0x29, 0x64, 0x38, 0x75, 0xa2,
0xef, 0x41, 0x0c, 0xdb, 0x96, 0x42, 0x0f, 0xd8, 0x95, 0x3b, 0x76, 0xa1,
0xec, 0xb0, 0xfd, 0x2a, 0x67, 0xc9, 0x84, 0x53, 0x1e, 0xeb, 0xa6, 0x71,
0x3c, 0x92, 0xdf, 0x08, 0x45, 0x19, 0x54, 0x83, 0xce, 0x60, 0x2d, 0xfa,
0xb7, 0x5d, 0x10, 0xc7, 0x8a, 0x24, 0x69, 0xbe, 0xf3, 0xaf, 0xe2, 0x35,
0x78, 0xd6, 0x9b, 0x4c, 0x01, 0xf4, 0xb9, 0x6e, 0x23, 0x8d, 0xc0, 0x17,
0x5a, 0x06, 0x4b, 0x9c, 0xd1, 0x7f, 0x32, 0xe5, 0xa8
};
uint8_t CalCRC8(uint8_t *p, uint8_t len){
uint8_t crc = 0;
uint16_t i;
for (i = 0; i < len; i++){
crc = CrcTable[(crc ^ *p++) & 0xff];
}
return crc;
}

二、测量数据分析
每个测量数据点由 2 个字节长度的距离值和 1 个字节长度的置信度值组
成,如下图所示。
在这里插入图片描述
距离值的单位为 mm。信号强度值反映的是光反射强度,强度越高,信号强
度值越大;强度越低,信号强度值越小。
每个点的角度值是通过起始角度和结束角度线性插值得来,其角度计算方法
如下:

step = (end_angle – start_angle)/(len – 1);
angle = start_angle + step*i;
其中 len 为一个数据包的测量点数,i 的取值范围为[0 , len )

参考示例

	假设我们收到了如下所示的一段数据

54 2C 68 08 AB 7E E0 00 E4 DC 00 E2 D9 00 E5 D5 00 E3 D3 00 E4 D0 00 E9 CD
00 E4 CA 00 E2 C7 00 E9 C5 00 E5 C2 00 E5 C0 00 E5 BE 82 3A 1A 50

在这里插入图片描述

STM32F103C8T6配置

1、配置RCC时钟、选择外部晶振,下载方式
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

2、配置USART1和USART3(配置默认的就可以了,波特率都是115200),都开启中断
在这里插入图片描述
在这里插入图片描述
3、指示灯的配置。一般是设置成推挽输出。在代码中的作用是闪烁,就是为了提醒我们代码还在运行(一般不设置也是可以的)
在这里插入图片描述

4、工程文件设置。一般选择:只需要设复制.C和.H文件。每个外设成立单独的.C和.H文件在这里插入图片描述

5、代码

/* USER CODE BEGIN Header */
/**
  ******************************************************************************
  * @file           : main.c
  * @brief          : Main program body
  ******************************************************************************
  * @attention
  *
  * Copyright (c) 2022 STMicroelectronics.
  * All rights reserved.
  *
  * This software is licensed under terms that can be found in the LICENSE file
  * in the root directory of this software component.
  * If no LICENSE file comes with this software, it is provided AS-IS.
  *
  ******************************************************************************
  */
/* USER CODE END Header */
/* Includes ------------------------------------------------------------------*/
#include "main.h"
#include "usart.h"
#include "gpio.h"

/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */
#include "stdio.h"
#include "string.h"
/* USER CODE END Includes */
typedef struct __attribute__((packed)) Point_Data
{
	uint16_t distance;//距离
	uint8_t confidence;//置信度
	
}LidarPointStructDef;

typedef struct __attribute__((packed)) Pack_Data
{
	uint8_t header;
	uint8_t ver_len;
	uint16_t speed;
	uint16_t start_angle;
	LidarPointStructDef point[POINT_PER_PACK];
	uint16_t end_angle;
	uint16_t timestamp;
	uint8_t crc8;
}LiDARFrameTypeDef;
typedef struct __attribute__((packed)) PointDataProcess_
{
	uint16_t distance;//角度
	float angle;//距离
}PointDataProcessDef;//经过处理后的数据

LiDARFrameTypeDef Pack_Data;			//雷达接收的数据储存在这个变量之中
PointDataProcessDef AvoidData[100];
/* Private typedef -----------------------------------------------------------*/
/* USER CODE BEGIN PTD */

/* USER CODE END PTD */

/* Private define ------------------------------------------------------------*/
/* USER CODE BEGIN PD */
/* USER CODE END PD */

/* Private macro -------------------------------------------------------------*/
/* USER CODE BEGIN PM */

/* USER CODE END PM */

/* Private variables ---------------------------------------------------------*/

/* USER CODE BEGIN PV */

/* USER CODE END PV */

/* Private function prototypes -----------------------------------------------*/
void SystemClock_Config(void);
/* USER CODE BEGIN PFP */

/* USER CODE END PFP */

/* Private user code ---------------------------------------------------------*/
/* USER CODE BEGIN 0 */
uint8_t Uart3_Receive_buf[1];  
int count;
int data_process(void);
/* USER CODE END 0 */

/**
  * @brief  The application entry point.
  * @retval int
  */
int main(void)
{
  /* USER CODE BEGIN 1 */
	int i;

  /* USER CODE END 1 */

  /* MCU Configuration--------------------------------------------------------*/

  /* Reset of all peripherals, Initializes the Flash interface and the Systick. */
  HAL_Init();

  /* USER CODE BEGIN Init */

  /* USER CODE END Init */

  /* Configure the system clock */
  SystemClock_Config();

  /* USER CODE BEGIN SysInit */

  /* USER CODE END SysInit */

  /* Initialize all configured peripherals */
  MX_GPIO_Init();
  MX_USART1_UART_Init();
  MX_USART3_UART_Init();
  /* USER CODE BEGIN 2 */
	HAL_UART_Receive_IT(&huart3,Uart3_Receive_buf,sizeof(Uart3_Receive_buf));

  /* USER CODE END 2 */

  /* Infinite loop */
  /* USER CODE BEGIN WHILE */
  while (1)
  {
    /* USER CODE END WHILE */
		printf("接收成功%d\r\n",count);
		
		for(i=0;i<100;i++)
		{
			printf("角度:%f,距离:%d\r\n",AvoidData[i].angle,AvoidData[i].distance);
		}
		HAL_Delay(50);
    /* USER CODE BEGIN 3 */
  }
  /* USER CODE END 3 */
}

/**
  * @brief System Clock Configuration
  * @retval None
  */
void SystemClock_Config(void)
{
  RCC_OscInitTypeDef RCC_OscInitStruct = {0};
  RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};

  /** Initializes the RCC Oscillators according to the specified parameters
  * in the RCC_OscInitTypeDef structure.
  */
  RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE;
  RCC_OscInitStruct.HSEState = RCC_HSE_ON;
  RCC_OscInitStruct.HSEPredivValue = RCC_HSE_PREDIV_DIV1;
  RCC_OscInitStruct.HSIState = RCC_HSI_ON;
  RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
  RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE;
  RCC_OscInitStruct.PLL.PLLMUL = RCC_PLL_MUL9;
  if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK)
  {
    Error_Handler();
  }
  /** Initializes the CPU, AHB and APB buses clocks
  */
  RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK
                              |RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2;
  RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;
  RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
  RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV2;
  RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1;

  if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_2) != HAL_OK)
  {
    Error_Handler();
  }
}
int data_process(void)
{
	static uint8_t data_cnt_avoid = 0;		//避障模式数组计数变量
	uint8_t i = 0;
	float start_angle = Pack_Data.start_angle/100.0;//计算一帧16个点的开始角度
	float end_angel = Pack_Data.end_angle/100.0;	//结束角度
	if(start_angle>=360)
		start_angle -= 360;
	if(end_angel>=360)
		end_angel -= 360;
	float average_angel;				//定义一帧数据平均角度
	//180度的地方是车头,做一定的转换使车头方向是0度
	if((start_angle -= 180)<0)
		start_angle += 360;
	if((end_angel -= 180)<0)
		end_angel += 360;
	if(start_angle>end_angel) end_angel +=360;
	average_angel = (end_angel - start_angle)/11;
	if((start_angle>310||end_angel<50))
	{
		for(i=0;i<12;i++)
		{
			AvoidData[data_cnt_avoid].angle = start_angle + i*average_angel;
			if(AvoidData[data_cnt_avoid].angle>=360)
				AvoidData[data_cnt_avoid].angle -= 360;
			AvoidData[data_cnt_avoid].distance = Pack_Data.point[i].distance;
			if(++data_cnt_avoid == 100)
				data_cnt_avoid = 0;
		}
	}
	return 0;
	
}

/* USER CODE BEGIN 4 */
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
  if(huart == &huart3)
	{
		static uint8_t state = 0;//状态位	,指示当前数据帧的位置
	  static uint8_t crc = 0;	//校验和
	  static uint8_t cnt = 0;	//用于一帧12个点的计数
	  uint8_t temp_data; 
		temp_data=Uart3_Receive_buf[0]; 

			if (state > 5)	
			{
				if(state < 42)
				{
					if(state%3 == 0)		//一帧数据中的序号为6,9.....39的数据,距离值低8位
					{
						Pack_Data.point[cnt].distance = (uint16_t)temp_data;
						state++;
						crc = CrcTable[(crc^temp_data) & 0xff];
					}
					else if(state%3 == 1)	//一帧数据中的序号为7,10.....40的数据,距离值高8位
					{
						Pack_Data.point[cnt].distance = ((uint16_t)temp_data<<8)+Pack_Data.point[cnt].distance;
						state++;
						crc = CrcTable[(crc^temp_data) & 0xff];
					}
					else					//一帧数据中的序号为8,11.....41的数据,置信度
					{
						Pack_Data.point[cnt].confidence = temp_data;
						cnt++;	
						state++;
						crc = CrcTable[(crc^temp_data) & 0xff];
					}
				}
				else 
				{
					switch(state)
					{
						case 42:
							Pack_Data.end_angle = (uint16_t)temp_data;						//结束角度低8位
							state++;
							crc = CrcTable[(crc^temp_data) & 0xff];
							break;
						case 43:
							Pack_Data.end_angle = ((uint16_t)temp_data<<8)+Pack_Data.end_angle;//结束角度高8位
							state++;
							crc = CrcTable[(crc^temp_data) & 0xff];
							break;
						case 44:
							Pack_Data.timestamp = (uint16_t)temp_data;						//时间戳低8位
							state++;
							crc = CrcTable[(crc^temp_data) & 0xff];
							break;
						case 45:
							Pack_Data.timestamp = ((uint16_t)temp_data<<8)+Pack_Data.timestamp;//时间戳高8位
							state++;
							crc = CrcTable[(crc^temp_data) & 0xff];
							break;
						case 46:
							Pack_Data.crc8 = temp_data;		//雷达传来的校验和
							if(Pack_Data.crc8 == crc)		//校验正确
							{
								count++;
								data_process();				//接收到一帧且校验正确可以进行数据处理
								
							}
							else
							{
								memset(&Pack_Data, 0, sizeof(LiDARFrameTypeDef));//清零
							}
							crc = 0;
							state = 0;
							cnt = 0;//复位
							break;
						default: break;
					}
				}
			}
			else 
			{
				switch(state)
				{
					case 0:
						if(temp_data == HEADER)									//头固定
						{
							Pack_Data.header = temp_data;
							state++;
							crc = CrcTable[(crc^temp_data) & 0xff];				//开始进行校验
						} else state = 0,crc = 0;
						break;
					case 1:
						if(temp_data == LENGTH)									//测量的点数,目前固定
						{
							Pack_Data.ver_len = temp_data;
							state++;
							crc = CrcTable[(crc^temp_data) & 0xff];
						} else state = 0,crc = 0;
						break;
					case 2:
						Pack_Data.speed = (uint16_t)temp_data;						//雷达的转速低8位,单位度每秒
						state++;
						crc = CrcTable[(crc^temp_data) & 0xff];
						break;
					case 3:
						Pack_Data.speed = ((uint16_t)temp_data<<8)+Pack_Data.speed;	//雷达的转速高8位
						state++;
						crc = CrcTable[(crc^temp_data) & 0xff];
						break;
					case 4:
						Pack_Data.start_angle = (uint16_t)temp_data;					//开始角度低8位,放大了100倍
						state++;
						crc = CrcTable[(crc^temp_data) & 0xff];
						break;
					case 5:
						Pack_Data.start_angle = ((uint16_t)temp_data<<8)+Pack_Data.start_angle;
						state++;
						crc = CrcTable[(crc^temp_data) & 0xff];
						break;
					default: break;
				}
			}

	HAL_UART_Receive_IT(&huart3,Uart3_Receive_buf,sizeof(Uart3_Receive_buf));//串口5回调函数执行完毕之后,需要再次开启接收中断等待下一次接收中断的发生	
	}
}

/* USER CODE END 4 */

/**
  * @brief  This function is executed in case of error occurrence.
  * @retval None
  */
void Error_Handler(void)
{
  /* USER CODE BEGIN Error_Handler_Debug */
  /* User can add his own implementation to report the HAL error return state */
  __disable_irq();
  while (1)
  {
  }
  /* USER CODE END Error_Handler_Debug */
}

#ifdef  USE_FULL_ASSERT
/**
  * @brief  Reports the name of the source file and the source line number
  *         where the assert_param error has occurred.
  * @param  file: pointer to the source file name
  * @param  line: assert_param error line source number
  * @retval None
  */
void assert_failed(uint8_t *file, uint32_t line)
{
  /* USER CODE BEGIN 6 */
  /* User can add his own implementation to report the file name and line number,
     ex: printf("Wrong parameters value: file %s on line %d\r\n", file, line) */
  /* USER CODE END 6 */
}
#endif /* USE_FULL_ASSERT */


评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值