本次使用的是乐动14激光雷达
目录
原理与工作方式介绍:
LD14雷达采用的是三角测量法(Triangulation Method)来实现测距功能。它通过发射激光束,并接收被目标物体反射回来的激光信号,通过计算激光传播的时间或角度差来确定目标物体与雷达的距离。
具体而言,LD14从一个固定的角度发射出红外激光束,该激光束遇到目标物体后会被反射回来,并被接收单元接收。通过测量从激光发射到接收的时间间隔或者测量接收激光的角度,LD14利用三角关系计算出目标物体与雷达之间的距离。
在测距过程中,LD14通过内部的激光发射器将激光束发送出去,然后使用接收单元接收反射回来的激光。接收单元可以是光电元件,如光敏二极管或光电二极管。通过测量激光从发射到接收的时间间隔,LD14可以根据光在空气中的传播速度计算出距离。
此外,LD14还会融合角度测量单元测量到的角度值和测距得到的距离数据,形成点云数据。然后通过无线通讯将这些点云数据发送到外部接口,以便进一步处理和分析。
另外,为了保持激光束的精确定位和扫描范围,LD14还采用电机驱动单元来控制发射器或整个雷达的转动,通过PID算法实现闭环控制,使其转速能够精确控制在指定范围内。
通讯协议:
数据包格式
⚫ 起始符:长度 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 校验:前面所有数据的校验和;
程序讲解:
STM32版:
串口初始化:
int PointDataProcess_count=0,test_once_flag=0,Dividing_point=0;//雷达接收数据点的计算值、接收到一圈数据最后一帧数据的标志位、需要切割数据的数据数
LiDARFrameTypeDef Pack_Data;//雷达接收的数据储存在这个变量之中
u8 one_lap_data_success_flag=0; //雷达数据完成一圈的接收标志位
int lap_count=0;//当前雷达这一圈数据有多少个点
extern u16 receive_cnt;
PointDataProcessDef PointDataProcess[420];//更新390个数据
PointDataProcessDef Dataprocess[400]; //用于小车避障、跟随、走直线、ELE雷达避障的雷达数据
PointDataProcessDef TempData[12]={0}; //超过了0度的下一圈数据临时存储
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
};//用于crc校验的数组
void uart3_init(u32 bound)
{
//GPIO端口设置
GPIO_InitTypeDef GPIO_InitStructure;
USART_InitTypeDef USART_InitStructure;
NVIC_InitTypeDef NVIC_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE); //使能UGPIOB时钟
RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART3, ENABLE); //使能USART3时钟
//USART3_TX
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10; //PB.10
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; //复用推挽输出
GPIO_Init(GPIOB, &GPIO_InitStructure);
//USART3_RX
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_11;//PB11
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;//浮空输入
GPIO_Init(GPIOB, &GPIO_InitStructure);
//Usart3 NVIC 配置
NVIC_InitStructure.NVIC_IRQChannel = USART3_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=0 ;//抢占优先级
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1; //子优先级
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //IRQ通道使能
NVIC_Init(&NVIC_InitStructure); //根据指定的参数初始化VIC寄存器
//USART 初始化设置
USART_InitStructure.USART_BaudRate = bound;//串口波特率
USART_InitStructure.USART_WordLength = USART_WordLength_8b;//字长为8位数据格式
USART_InitStructure.USART_StopBits = USART_StopBits_1;//一个停止位
USART_InitStructure.USART_Parity = USART_Parity_No;//无奇偶校验位
USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;//无硬件数据流控制
USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx; //收发模式
USART_Init(USART3, &USART_InitStructure); //初始化串口3
USART_ITConfig(USART3, USART_IT_RXNE, ENABLE);//开启串口接受中断
USART_Cmd(USART3, ENABLE); //使能串口3
}
串口3接收中断:
void USART3_IRQHandler(void)//接收ld14雷达数据,一帧47个字节
{
static u8 state = 0;//状态位
static u8 crc = 0;//校验和
static u8 cnt = 0;//用于一帧12个点的计数
u8 temp_data;
if(USART_GetITStatus(USART3, USART_IT_RXNE) != RESET) //接收到数据
{
temp_data=USART_ReceiveData(USART3);
USART_ClearITPendingBit(USART3,USART_IT_RXNE);
if (state > 5)
{
if(state < 42)
{
if(state%3 == 0)//一帧数据中的序号为6,9.....39的数据,距离值低8位
{
Pack_Data.point[cnt].distance = (u16)temp_data;
state++;
crc = CrcTable[(crc^temp_data) & 0xff];
}
else if(state%3 == 1)//一帧数据中的序号为7,10.....40的数据,距离值高8位
{
Pack_Data.point[cnt].distance = ((u16)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 = (u16)temp_data;//结束角度低8位
state++;
crc = CrcTable[(crc^temp_data) & 0xff];
break;
case 43:
Pack_Data.end_angle = ((u16)temp_data<<8)+Pack_Data.end_angle;//结束角度高8位
state++;
crc = CrcTable[(crc^temp_data) & 0xff];
break;
case 44:
Pack_Data.timestamp = (u16)temp_data;//时间戳低8位
state++;
crc = CrcTable[(crc^temp_data) & 0xff];
break;
case 45:
Pack_Data.timestamp = ((u16)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)//校验正确
{
data_process();//接收到一帧且校验正确可以进行数据处理
receive_cnt++;//输出接收到正确数据的次数
}
else
memset(&Pack_Data,0,sizeof(Pack_Data));//清零
crc = 0;
state = 0;
cnt = 0;//复位
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 = (u16)temp_data;//雷达的转速低8位,单位度每秒
state++;
crc = CrcTable[(crc^temp_data) & 0xff];
break;
case 3:
Pack_Data.speed = ((u16)temp_data<<8)+Pack_Data.speed;//雷达的转速高8位
state++;
crc = CrcTable[(crc^temp_data) & 0xff];
break;
case 4:
Pack_Data.start_angle = (u16)temp_data;//开始角度低8位,放大了100倍
state++;
crc = CrcTable[(crc^temp_data) & 0xff];
break;
case 5:
Pack_Data.start_angle = ((u16)temp_data<<8)+Pack_Data.start_angle;
state++;
crc = CrcTable[(crc^temp_data) & 0xff];
break;
default: break;
}
}
}
}
- 通过USART_GetITStatus函数检查是否接收到数据,如果有数据则继续执行。
- 使用USART_ReceiveData函数获取接收到的数据,并将其保存在temp_data变量中。
- 调用USART_ClearITPendingBit函数清除接收中断标志位。
- 根据状态值state的不同,执行不同的操作:
- 当state大于5时,表示正在接收一帧数据的内容。
- 如果state在6到41之间,表示正在接收距离和置信度数据。
- 当state是6, 9, ... , 39时,表示接收到的是距离值的低8位,将其保存在Pack_Data.point[cnt].distance变量中,并更新校验和crc。
- 当state是7, 10, ..., 40时,表示接收到的是距离值的高8位,将其与之前保存的低8位进行组合,并保存在Pack_Data.point[cnt].distance变量中,并更新校验和crc。
- 当state是8, 11, ..., 41时,表示接收到的是置信度值,将其保存在Pack_Data.point[cnt].confidence变量中,并更新校验和crc。然后,增加计数cnt并更新状态state。
- 当state大于41时,表示接收到的是结束角度、时间戳和校验和数据。
- 当state是42时,表示接收到的是结束角度的低8位,将其保存在Pack_Data.end_angle变量中,并更新校验和crc。
- 当state是43时,表示接收到的是结束角度的高8位,将其与之前保存的低8位进行组合,并保存在Pack_Data.end_angle变量中,并更新校验和crc。
- 当state是44时,表示接收到的是时间戳的低8位,将其保存在Pack_Data.timestamp变量中,并更新校验和crc。
- 当state是45时,表示接收到的是时间戳的高8位,将其与之前保存的低8位进行组合,并保存在Pack_Data.timestamp变量中,并更新校验和crc。
- 当state是46时,表示接收到的是雷达传来的校验和数据。如果接收到的校验和与计算出的校验和一致,则调用data_process函数进行数据处理,并增加接收计数receive_cnt。否则,将Pack_Data结构体清零,并重置变量crc、state和cnt为初始值。
- 如果state在6到41之间,表示正在接收距离和置信度数据。
- 当state小于等于5时,表示正在接收一帧数据的头部信息。
- 当state是0时,表示接收到的是头固定值。如果接收到的值与预设的值相等,则将其保存在Pack_Data.header变量中,并开始进行校验。否则,将state和crc重置为初始值。
- 当state是1时,表示接收到的是测量的点数。如果接收到的值与预设的值相等,则将其保存在Pack_Data.ver_len变量中,并更新校验和crc。否则,将state和crc重置为初始值。
- 当state是2时,表示接收到的是雷达的转速低8位,将其保存在Pack_Data.speed变量中,并更新校验和crc。
- 当state是3时,表示接收到的是雷达的转速高8位,将其与之前保存的低8位进行组合,并保存在Pack_Data.speed变量中,并更新校验和crc。
- 当state是4时,表示接收到的是开始角度的低8位,将其保存在Pack_Data.start_angle变量中,并更新校验和crc。
- 当state是5时,表示接收到的是开始角度的高8位,将其与之前保存的低8位进行组合,并保存在Pack_Data.start_angle变量中,并更新校验和crc。
- 当state大于5时,表示正在接收一帧数据的内容。
数据处理函数
完成一帧之后可进行数据处理
void data_process(void)//数据处理函数,完成一帧之后可进行数据处理
{
//雷达每帧数据是固定12个点,雷达一圈大约有xx个点但是不是一个固定值。雷达一秒转大约xx圈,
//程序是等雷达转完1圈后再将一整圈的数据来使用而不是一帧一帧数据的实时更新而是获取到一整圈雷达数据后再更新。
//由于雷达每一圈的点数都不是固定的(因为转速不是恒定会有微微小偏差),所以每一圈的倒数第一帧数据很大概率是“前半段数据是当前这一圈的末尾,后半段数据是下一圈的开头”,
//因此雷达数据处理的时候会根据这一帧数据的开始角度和结束角度判断是否对数据进行切割。
u16 i,j,m;
float start_angle = Pack_Data.start_angle/100.0; //计算12个点的开始角度,数字传输时放大了100倍
float end_angle = Pack_Data.end_angle/100.0;
float area_angle[12]={0};//一帧数据的平均角度
//180度的地方是车头,做一定的转换使车头方向是0度
if((start_angle -= 180)<0)
start_angle += 360;
if((end_angle -= 180)<0)
end_angle += 360;
if(start_angle>end_angle)//开始和结束角度被0度刚好分开的情况
{
end_angle +=360;
}
for(m=0;m<12;m++) //获取每个点的角度值
{
area_angle[m]=start_angle+(end_angle-start_angle)/10.5*m;
if(area_angle[m]>360.0) //情况1:开始和结束角度被0度刚好分开的情况
{
if(test_once_flag==0) //找到第几个点是分界点
{
test_once_flag=1; //找到了标志位置1
Dividing_point=m;
area_angle[m] -=360; //还原点的正确角度值
}
else area_angle[m] -=360; //还原点的正确角度值
}
}
if ((end_angle <= 360) && (end_angle >= 359) ) //情况2:特殊情况,刚好数据不被0点切割,0点是被两帧数据都夹在中间
{
test_once_flag=1;
Dividing_point=12;
}
if(test_once_flag) //这帧数据要做数据切割的情况(这帧数据包含了这一圈的数据和下一圈的数据)
{
for(i=0;i<Dividing_point;i++) //这一帧数据360度之前的点正常保留
{
PointDataProcess[PointDataProcess_count+i].angle = area_angle[i];
PointDataProcess[PointDataProcess_count+i].distance = Pack_Data.point[i].distance;
}
PointDataProcess_count=PointDataProcess_count+Dividing_point;
for(j=0;j<12-Dividing_point;j++) //把下一圈数据存放在临时存储数组里
{
TempData[j].angle = area_angle[j+Dividing_point];
TempData[j].distance = Pack_Data.point[j+Dividing_point].distance;
}
for(m=0;m<PointDataProcess_count;m++) //这时一整圈的数据已经收集完成,把一整圈数据复制到另一个数组进行使用
{
Dataprocess[m].angle=PointDataProcess[m].angle;
Dataprocess[m].distance = PointDataProcess[m].distance;
}
lap_count=PointDataProcess_count;//获取当前的这一圈有多少个点
one_lap_data_success_flag=1;//一圈数据更新完成
test_once_flag=0; //标志位清零,下一帧数据必定不需要做数据切割
PointDataProcess_count=0;//计算第几个点的计算变量清零
for (m = 0; m < lap_count; m++)
{
if (Dataprocess[m].angle >= -150 && Dataprocess[m].angle <= 30) { // 只输出角度在-90度到90度之间的数据
printf("Angle: %f\r\nDistance: %f\r\n", Dataprocess[m].angle, Dataprocess[m].distance*1000);
delay_ms(500);
}
}
}
else//这组数据不需要做数据切割
{
for(i=0;i<12;i++) //把一帧数据存放在distance_sum数组里
{
if(one_lap_data_success_flag) //如果上一帧数据做了切割,那么就要先把之前临时存储的数据取出来放在这一圈数据的最前面
{
for(j=0;j<12-Dividing_point;j++)
{
PointDataProcess[j].angle = TempData[j].angle;
PointDataProcess[j].distance = TempData[j].distance;
}
one_lap_data_success_flag=0;//现在是新的一圈数据,置零
PointDataProcess_count=PointDataProcess_count+(12-Dividing_point);//累加
Dividing_point=0;//判断切割点的数清零
}
PointDataProcess[PointDataProcess_count+i].angle = area_angle[i];
PointDataProcess[PointDataProcess_count+i].distance = Pack_Data.point[i].distance;
}
PointDataProcess_count=PointDataProcess_count+12; //累加12
// 输出存储的角度和距离数据
for (m = 0; m < PointDataProcess_count; m++)
{
printf("Angle: %f\r\nDistance: %f\r\n", PointDataProcess[m].angle, PointDataProcess[m].distance*1000);
delay_ms(500);
}
}
}
分析讲解如下:
- 通过判断开始角度和结束角度来确定一帧数据的平均角度范围。
- 对开始和结束角度进行转换,使车头的方向为0度。
- 判断是否需要对数据进行切割,即判断该帧数据是否包含了当前一圈和下一圈的数据。
- 如果需要进行数据切割,将当前一圈的数据存储到
PointDataProcess
数组中,并将下一圈的数据存储到临时数组TempData
中。 - 复制整圈数据到另一个数组
Dataprocess
中,标志一圈数据更新完成。 - 如果不需要进行数据切割,直接将一帧数据存储到
PointDataProcess
数组中。 - 输出存储的角度和距离数据,只输出角度在-150度到30度之间的数据。
需要注意的是,代码中存在一些变量和标志位的使用,例如test_once_flag
、Dividing_point
、PointDataProcess_count
、one_lap_data_success_flag
等,这些变量用于辅助数据处理过程,并根据需要进行相应的操作。
此外,代码中还有一些细节处理,例如处理开始和结束角度被0度刚好分开的情况、处理特殊情况下0点被两帧数据夹在中间的情况等。
MSP432版
串口初始化:
int PointDataProcess_count=0,test_once_flag=0,Dividing_point=0;//雷达接收数据点的计算值、接收到一圈数据最后一帧数据的标志位、需要切割数据的数据数
LiDARFrameTypeDef Pack_Data;//雷达接收的数据储存在这个变量之中
uint8_t one_lap_data_success_flag=0; //雷达数据完成一圈的接收标志位
int lap_count=0;//当前雷达这一圈数据有多少个点
extern uint16_t receive_cnt;
PointDataProcessDef PointDataProcess[420];//更新390个数据
PointDataProcessDef Dataprocess[400]; //用于小车避障、跟随、走直线、ELE雷达避障的雷达数据
PointDataProcessDef TempData[12]={0}; //超过了0度的下一圈数据临时存储
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
};//用于crc校验的数组
void uart3_init(uint32_t baudRate)
{
//1.配置GPIO复用
GPIO_setAsPeripheralModuleFunctionInputPin(GPIO_PORT_P2, GPIO_PIN2 | GPIO_PIN3, GPIO_PRIMARY_MODULE_FUNCTION);
//2.配置UART结构体
#ifdef EUSCI_A_UART_7_BIT_LEN
//固件库v3_40_01_02
//默认SMCLK 48MHz 比特率 115200
const eUSCI_UART_ConfigV1 uariig =
{
EUSCI_A_UART_CLOCKSOURCE_SMCLK, // SMCLK Clock Source
26, // BRDIV = 26
0, // UCxBRF = 0
111, // UCxBRS = 111
EUSCI_A_UART_NO_PARITY, // No Parity
EUSCI_A_UART_LSB_FIRST, // MSB First
EUSCI_A_UART_ONE_STOP_BIT, // One stop bit
EUSCI_A_UART_MODE, // UART mode
EUSCI_A_UART_OVERSAMPLING_BAUDRATE_GENERATION, // Oversampling
EUSCI_A_UART_8_BIT_LEN // 8 bit data length
};
eusci_calcBaudDividers((eUSCI_UART_ConfigV1 *)&uariig, 115200); //配置波特率
#else
//固件库v3_21_00_05
//默认SMCLK 48MHz 比特率 115200
const eUSCI_UART_Config uariig =
{
EUSCI_A_UART_CLOCKSOURCE_SMCLK, // SMCLK Clock Source
26, // BRDIV = 26
0, // UCxBRF = 0
111, // UCxBRS = 111
EUSCI_A_UART_NO_PARITY, // No Parity
EUSCI_A_UART_LSB_FIRST, // MSB First
EUSCI_A_UART_ONE_STOP_BIT, // One stop bit
EUSCI_A_UART_MODE, // UART mode
EUSCI_A_UART_OVERSAMPLING_BAUDRATE_GENERATION, // Oversampling
};
eusci_calcBaudDividers((eUSCI_UART_Config *)&uariig, 115200); //配置波特率
#endif
//3.初始化串口
UART_initModule(EUSCI_A1_BASE, &uariig);
//4.开启串口模块
UART_enableModule(EUSCI_A1_BASE);
//5.开启串口相关中断
UART_enableInterrupt(EUSCI_A1_BASE, EUSCI_A_UART_RECEIVE_INTERRUPT);
//6.开启串口端口中断
Interrupt_enableInterrupt(INT_EUSCIA1);
}
串口中断函数:
void EUSCIA1_IRQHandler(void)
{
static uint8_t state = 0;//状态位
static uint8_t crc = 0;//校验和
static uint8_t cnt = 0;//用于一帧12个点的计数
uint8_t temp_data;
if (MAP_UART_getInterruptStatus(EUSCI_A1_BASE, EUSCI_A_UART_RECEIVE_INTERRUPT_FLAG) != 0)
{
temp_data = UART_receiveData(EUSCI_A1_BASE);
UART_clearInterruptFlag(EUSCI_A1_BASE, EUSCI_A_UART_RECEIVE_INTERRUPT_FLAG);
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) //校验正确
{
data_process(); //接收到一帧且校验正确可以进行数据处理
receive_cnt++; //输出接收到正确数据的次数
}
else
memset(&Pack_Data, 0, sizeof(Pack_Data)); //清零
crc = 0;
state = 0;
cnt = 0; //复位
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;
}
}
}
}
数据处理函数:
void data_process(void)//数据处理函数,完成一帧之后可进行数据处理
{
//雷达每帧数据是固定12个点,雷达一圈大约有xx个点但是不是一个固定值。雷达一秒转大约xx圈,
//程序是等雷达转完1圈后再将一整圈的数据来使用而不是一帧一帧数据的实时更新而是获取到一整圈雷达数据后再更新。
//由于雷达每一圈的点数都不是固定的(因为转速不是恒定会有微微小偏差),所以每一圈的倒数第一帧数据很大概率是“前半段数据是当前这一圈的末尾,后半段数据是下一圈的开头”,
//因此雷达数据处理的时候会根据这一帧数据的开始角度和结束角度判断是否对数据进行切割。
uint16_t i,j,m;
float start_angle = Pack_Data.start_angle/100.0; //计算12个点的开始角度,数字传输时放大了100倍
float end_angle = Pack_Data.end_angle/100.0;
float area_angle[12]={0};//一帧数据的平均角度
//180度的地方是车头,做一定的转换使车头方向是0度
if((start_angle -= 180)<0)
start_angle += 360;
if((end_angle -= 180)<0)
end_angle += 360;
if(start_angle>end_angle)//开始和结束角度被0度刚好分开的情况
{
end_angle +=360;
}
for(m=0;m<12;m++) //获取每个点的角度值
{
area_angle[m]=start_angle+(end_angle-start_angle)/10.5*m;
if(area_angle[m]>360.0) //情况1:开始和结束角度被0度刚好分开的情况
{
if(test_once_flag==0) //找到第几个点是分界点
{
test_once_flag=1; //找到了标志位置1
Dividing_point=m;
area_angle[m] -=360; //还原点的正确角度值
}
else area_angle[m] -=360; //还原点的正确角度值
}
}
if ((end_angle <= 360) && (end_angle >= 359) ) //情况2:特殊情况,刚好数据不被0点切割,0点是被两帧数据都夹在中间
{
test_once_flag=1;
Dividing_point=12;
}
if(test_once_flag) //这帧数据要做数据切割的情况(这帧数据包含了这一圈的数据和下一圈的数据)
{
for(i=0;i<Dividing_point;i++) //这一帧数据360度之前的点正常保留
{
PointDataProcess[PointDataProcess_count+i].angle = area_angle[i];
PointDataProcess[PointDataProcess_count+i].distance = Pack_Data.point[i].distance;
}
PointDataProcess_count=PointDataProcess_count+Dividing_point;
for(j=0;j<12-Dividing_point;j++) //把下一圈数据存放在临时存储数组里
{
TempData[j].angle = area_angle[j+Dividing_point];
TempData[j].distance = Pack_Data.point[j+Dividing_point].distance;
}
for(m=0;m<PointDataProcess_count;m++) //这时一整圈的数据已经收集完成,把一整圈数据复制到另一个数组进行使用
{
Dataprocess[m].angle=PointDataProcess[m].angle;
Dataprocess[m].distance = PointDataProcess[m].distance;
}
lap_count=PointDataProcess_count;//获取当前的这一圈有多少个点
one_lap_data_success_flag=1;//一圈数据更新完成
test_once_flag=0; //标志位清零,下一帧数据必定不需要做数据切割
PointDataProcess_count=0;//计算第几个点的计算变量清零
for (m = 0; m < lap_count; m++)
{
if (Dataprocess[m].angle >= -150 && Dataprocess[m].angle <= 30) { // 只输出角度在-90度到90度之间的数据
printf("Angle: %f\r\nDistance: %d\r\n", Dataprocess[m].angle, Dataprocess[m].distance*1000);
delay_ms(500);
}
}
}
else//这组数据不需要做数据切割
{
for(i=0;i<12;i++) //把一帧数据存放在distance_sum数组里
{
if(one_lap_data_success_flag) //如果上一帧数据做了切割,那么就要先把之前临时存储的数据取出来放在这一圈数据的最前面
{
for(j=0;j<12-Dividing_point;j++)
{
PointDataProcess[j].angle = TempData[j].angle;
PointDataProcess[j].distance = TempData[j].distance;
}
one_lap_data_success_flag=0;//现在是新的一圈数据,置零
PointDataProcess_count=PointDataProcess_count+(12-Dividing_point);//累加
Dividing_point=0;//判断切割点的数清零
}
PointDataProcess[PointDataProcess_count+i].angle = area_angle[i];
PointDataProcess[PointDataProcess_count+i].distance = Pack_Data.point[i].distance;
}
PointDataProcess_count=PointDataProcess_count+12; //累加12
// 输出存储的角度和距离数据
for (m = 0; m < PointDataProcess_count; m++)
{
printf("Angle: %f\r\nDistance: %d\r\n", PointDataProcess[m].angle, PointDataProcess[m].distance*1000);
delay_ms(500);
}
}
}
演示: