嵌入式Linux车载ADAS驱动开发:对接毫米波雷达的点云数据实时解析
车载ADAS里毫米波雷达常走UART数据口或车载以太网上送点云/目标列表。Linux侧别指望“open设备节点就能拿到结构体”,真实数据是二进制字节流:帧同步头+帧头+TLV块。驱动层负责收字节、对齐帧边界;应用层负责解析点云、时间对齐、送感知线程。
一、接入方式:配置口与数据口分开
TI系模块典型是两路串口:一路CLI发cfg配置(chirp参数、帧率、输出TLV类型),一路高速数据口出点云;以太网方案(DCA1000类)则走UDP原始流。
Linux下用termios配数据口:
int radar_open(const char*dev){
int fd=open(dev,O_RDWR|O_NOCTTY);
struct termios t; tcgetattr(fd,&t);
cfsetispeed(&t,B921600); cfsetospeed(&t,B921600);
t.c_cflag |= CS8|CREAD|CLOCAL; t.c_lflag=0; t.c_iflag=0;
t.c_oflag=0; t.c_cc[VTIME]=0; t.c_cc[VMIN]=0;
tcsetattr(fd,TCSANOW,&t); return fd;
}
CLI口只在启动时写配置,写完后关掉;数据口常开,单独采集线程读。
二、帧同步:按Magic Word重对齐
雷达持续吐流,中间丢几字节就会整体错位。正确做法是环形缓冲收数据,每次搜Magic Word重新同步,不依赖“上次帧长”。
static const uint8_t MAGIC[8]={0x02,0x01,0x04,0x03,0x06,0x05,0x08,0x07};
int find_magic(uint8_t*buf,int len){
for(int i=0;i+8<=len;i++)
if(!memcmp(buf+i,MAGIC,8)) return i;
return -1;
}
帧头里取total_length、frame_num、num_tlv,校验长度够了再解析,不够留着下次拼。
三、TLV解析点云
TLV结构就是type+length+payload,点云块通常是若干点连续排列:
typedef struct{ float x,y,z,doppler; uint16_t snr; }__attribute__((packed)) point_t;
void parse_tlv(uint8_t*p,uint32_t len,point_t*out,int*pcnt){
uint32_t off=0;
while(off+4<=len){
uint32_t type=*(uint32_t*)(p+off); // 端序按雷达SDK定
uint32_t sz =*(uint32_t*)(p+off+4);
uint8_t *pl=p+off+8;
if(type==POINT_CLOUD_TLV){
int n=sz/sizeof(point_t);
for(int i=0;i<n && *pcnt<MAX_PT;i++){
point_t pt; memcpy(&pt,pl+i*sizeof(point_t),sizeof(pt));
out[(*pcnt)++]=pt;
}
}
off+=8+sz;
}
}
注意三坑:
端序:x/y/z是float小端还是大端,跟芯片SDK走
符号:距离/坐标负数别当uint16解析
单位:米还是厘米、速度是m/s还是放大100倍,必须按协议文档换算
四、实时采集:双缓冲+实时线程
ADAS不允许在主线程里read阻塞、也不允许每帧malloc。做法:
采集线程SCHED_FIFO,绑隔离核
双缓冲:dma/串口读到buf A,解析完交换给感知线程用buf B
只拷贝一次,解析后点云数组送下游
打时间戳用CLOCK_MONOTONIC,跨传感器靠PTP/帧号对齐
struct sched_param sp={.sched_priority=80};
pthread_setschedparam(pthread_self(),SCHED_FIFO,&sp);
cpu_set_t cpuset; CPU_ZERO(&cpuset); CPU_SET(2,&cpuset);
pthread_setaffinity_np(pthread_self(),sizeof(cpuset),&cpuset);
串口场景不是真正零拷贝,但双缓冲能避免解析期覆盖、减少系统调用次数;以太网DMA场景才可走mmap ring buffer进一步省拷贝。
主循环伪代码:
uint8_t rx[4096]; point_t frame[MAX_PT];
while(run){
int n=read(fd,rx,sizeof(rx));
ring_push(rx,n);
int sync=resync_magic();
if(sync>=0 && frame_complete()){
int cnt=0; parse_header(); parse_tlv(payload,plen,frame,&cnt);
swap_to_perception(frame,cnt,frame_no,timestamp);
}
}
五、ADAS工程落坑
波特率不够会丢帧:点云多就上921600或改以太网
帧号跳变要报警:连续丢帧可能是UART溢出或CPU被别的线程抢
解析线程别做聚类/跟踪:只出原始点云,聚类放感知线程
SNR阈值过滤:低信噪比点直接丢弃,减少误检
时间同步:雷达自带frame timestamp,和相机/LiDAR对齐用PTP,别用recv返回时刻
安全:雷达数据进ADAS决策前做范围校验,x/y/z超物理量程按无效点丢弃
调试:先用cat /dev/ttyXXX或python解析确认magic和点数,再写C;别一上来就进系统架构
按“配置口发cfg→数据口收流→Magic重同步→TLV取点云→双缓冲交换→实时线程送感知”落地,嵌入式Linux侧能把毫米波点云稳定接进ADAS流水线;重点不是“驱动多底层”,而是帧边界对齐、端序单位正确、采集线程不被调度抖动打断。要不要我再补一段AWR1843点云TLV偏移表,或SocketCAN+雷达目标级融合的写法?





