使用B210在Linux下实时处理ETC专用短程通信数据(2)-CPU单核高速数据处理

在上一篇文章中,使用Octave初步验证了ETC车联数据的格式。然而,Octave无法实时处理20M的采样带宽。我们本节通过C语言,重写 Octave程序,实现实时处理,涉及下面三个关键特点。

文章目录

  • 1. 全静态内存
  • 2. 使用环状缓存
  • 3 无滤波降采样
  • 4 运行效果
  • 5 完整代码

1. 全静态内存

要做到实时处理,首先要避免动态内存频繁分配。对于差分曼彻斯特编码来说,我们只关心一个符号内的窗口情况。

/*! 此文件实现 GB/T 20851.1 /.2 Uplink OBU --> RSU Class A*  差分曼彻斯特(FM0)调幅HDLCCEStdio 彩鹰工作室开发。GPLv3 @2025-06*/
#include "de_etc.h"
#include <cassert>
#include <cmath>
#include <cstdio>
#include <list>
#include <vector>
using namespace std;
namespace DE_ETC {namespace DE_ETC_OBU_CLASS_A {/*!* \brief spr 20MHz RX 5795MHz, window: 5785~5805,* 两个频位: 5790,5800MHz, Ettus B210 @ 20M IQ* OBU --> RSU Class A AM FM0 (差分曼彻斯特(FM0)调幅HDLC)*/
static const long long spr = 20000000;
//OBU侧为512KBD
static const long long brd = 512000;
//检测半窗口为2/5符号
static const long long detWin = spr * .4 / brd + .5;
//步进窗口为3/4符号
static const long long stepWin = spr * 3.0 / brd / 4;//检测窗口,左右各一半
vector<long long> bufJudge_0((size_t) detWin, (long long) 0);
vector<long long> bufJudge_1((size_t) detWin, (long long) 0);
//0 LEFT WIN  1 RIGHT WIN    detWin*2-1..detWin      detWin-1..0
static long long *ptrJudge[2]{bufJudge_0.data(), bufJudge_1.data()};
static long long sum[2]{0, 0};
static long long clk = 0, next_judge = 0, lost_cnt = 0;//解调突发缓存
vector<char> bits((size_t) 65536, '\0');
static char *ptr_bits = bits.data();
static char old_bit = 0;
static int total_bits = 0;}
}

由于懒的缘故,使用 vector作为连续内存的静态容器,并取得指针作为实际操作的操作符,尽量避免调用 vector的[] 重载函数。从上面的代码可以看出,对 OBU 侧的解调,检测窗口bufJudge_0、bufJudge_1的宽度均只有16个样点。

2. 使用环状缓存

解调的原理是在窗口bufJudge里,判断左侧的bufJudge_0的每个样点都小于均值,右侧bufJudge_1里的每个样点都大于均值,则为0,反之为1。

demod

为了避免反复统计均值,在上述窗口中,采用时钟clk % detWin的方式,把线性缓存变成环状缓存,动态刷新均值信息。

/*!* \brief append_etc_OBU_ClassA_20Miq 步骤01:实现ETC发送的波形的检测和解调。* \param data   输入IQ路数据* \param points 本波次数据长度*/
int append_etc_OBU_ClassA_20Miq(const short data[][2], const int points)
{using namespace DE_ETC_OBU_CLASS_A;if (points % down_sample){fprintf(stderr,"points must be N x down_sample points.\n");return 0;}//无滤波降采样频谱折叠,不影响两个频率的同时检测。ASK调幅不怕倒谱for (int i = 0; i < points; i += down_sample){const int pos = clk % detWin;//左统计窗口刷新sum[0] -= ptrJudge[0][pos];ptrJudge[0][pos] = ptrJudge[1][pos];sum[0] += ptrJudge[0][pos];//右统计窗口刷新sum[1] -= ptrJudge[1][pos];//AM解调ptrJudge[1][pos] = sqrt(data[i][0] * data[i][0] + data[i][1] * data[i][1])+.5;sum[1] += ptrJudge[1][pos];//...}//..
}

3 无滤波降采样

对于Linux台式机,上述两个技术已经完全满足实时处理的需要了。对于笔记本,因为CPU太满后,高温降频,极容易引起 B210 吞吐失败。为进一步降低CPU用量,可以进行无滤波降采样。

ASK虽然被PSK各种DIS,但是它既不怕频偏,也不怕钟差,更不怕倒谱,直接进行无滤波下抽,频谱合成后不影响调幅解调, 但噪声更大一些,应该根据CPU情况酌情调整这个值,建议取值1-4.

static const long long raw_spr = 20000000;
//由于ASK不怕倒谱,直接进行无滤波下抽,频谱合成后不影响调幅解调, 但性能更差,应该根据CPU情况酌情调整这个值,建议取值1-4.
static const long long down_sample = 2;
static const long long spr = raw_spr / down_sample  ;

两倍直接下抽后,窗口变为8个点,在比较老旧的CPU上也能完成处理。

4 运行效果

经过上述处理后,12.5秒的20M 16bit IQ数据(1GB字节)只要1-2秒就处理完了。可见,对于ASK解调和处理,再高一些采样率也可以接受,瓶颈在B210设备的稳定吞吐。


#include <cassert>
#include <cstdio>
#include <stdlib.h>
#include <time.h>
#include "de_etc.h"
#define BUFSIE 12000
int main(int argc, char * argv[])
{using namespace DE_ETC;FILE *fp = fopen("D:/ETC/up/5795.000000MHz_20.000000Sps","rb");short buf[BUFSIE][2];clock_t start = clock();int red = fread(buf, sizeof(buf[0]), BUFSIE, fp);while (red > 0){if (append_etc_OBU_ClassA_20Miq(buf, red)){auto data_vec = pop_etc_OBU_ClassA_20Miq();for (auto vec : data_vec ){const int sz = vec.size();for (int i=0;i<sz-2;++i)fprintf(stderr," %02X",(unsigned int)vec[i]);fprintf(stderr,"\n");}}red = fread(buf, sizeof(buf[0]), BUFSIE, fp);}clock_t endclock = clock();printf("\nUplink %d Clocks\n", endclock - start);fclose(fp);

clocks
在实时工作模式下,此模块的CPU消耗完全可控。

5 完整代码

完整的集成代码参考taskBus_course的第五部分。下面给出 downlink 的处理逻辑

/*! 此文件实现 GB/T 20851.1 /.2 Uplink OBU --> RSU Class A*  差分曼彻斯特(FM0)调幅HDLCCEStdio 彩鹰工作室开发。GPLv3 @2025-06*/
#include "de_etc.h"
#include <cassert>
#include <cmath>
#include <cstdio>
#include <list>
#include <vector>
using namespace std;
namespace DE_ETC {namespace DE_ETC_OBU_CLASS_A {/*!* \brief spr 20MHz RX 5795MHz, window: 5785~5805,* 两个频位: 5790,5800MHz, Ettus B210 @ 20M IQ* OBU --> RSU Class A AM FM0 (差分曼彻斯特(FM0)调幅HDLC)*/
static const long long raw_spr = 20000000;
//由于ASK不怕倒谱,直接进行无滤波下抽,频谱合成后不影响调幅解调, 但性能更差,应该根据CPU情况酌情调整这个值,建议取值1-2.
static const long long down_sample = 2;
static const long long spr = raw_spr / down_sample;
//OBU侧为512KBD
static const long long brd = 512000;
//检测半窗口为2/5符号
static const long long detWin = spr * .4 / brd + .5;
//步进窗口为3/4符号
static const long long stepWin = spr * 3.0 / brd / 4;//检测窗口,左右各一半
vector<long long> bufJudge_0((size_t) detWin, (long long) 0);
vector<long long> bufJudge_1((size_t) detWin, (long long) 0);
//0 LEFT WIN  1 RIGHT WIN    detWin*2-1..detWin      detWin-1..0
static long long *ptrJudge[2]{bufJudge_0.data(), bufJudge_1.data()};
static long long sum[2]{0, 0};
static long long clk = 0, next_judge = 0, lost_cnt = 0;//解调突发缓存
vector<char> bits((size_t) 65536, '\0');
static char *ptr_bits = bits.data();
static char old_bit = 0;
static int total_bits = 0;//HDLC提取缓存
vector<char> hdlc((size_t) 65536, '\0');
static char *ptr_hdlc = hdlc.data();
static int total_hdlc = 0;//GB/T-7496-1987 CRC 缓存
vector<char> crc01((size_t) 65536, '\0');
static char *ptr_crc01 = crc01.data();
vector<char> crc02((size_t) 65536, '\0');
static char *ptr_crc02 = crc02.data();//结果缓存
list<vector<unsigned char> > buf_resuls;
static int curr_mean  = 0;
/*!* \brief crc_GBT7496_1987 步骤04:CRC16 16,12,5,0 as GB-T7496-1987* \return CRC 为0表示成功*/
inline int crc_GBT7496_1987()
{const int info_len = total_hdlc - 16;for (int i = 0; i < info_len; ++i)ptr_crc01[i] = ptr_hdlc[i];for (int i = info_len; i < info_len + 16; ++i)ptr_crc01[i] = 0;for (int i = 0; i < info_len + 16; ++i)ptr_crc02[i] = i < 16 ? 1 : 0;for (int i = 0; i < info_len; ++i){if (ptr_crc01[i]){ptr_crc01[i] = 1 - ptr_crc01[i];ptr_crc01[i + 4] = 1 - ptr_crc01[i + 4];ptr_crc01[i + 11] = 1 - ptr_crc01[i + 11];ptr_crc01[i + 16] = 1 - ptr_crc01[i + 16];}if (ptr_crc02[i]){ptr_crc02[i] = 1 - ptr_crc02[i];ptr_crc02[i + 4] = 1 - ptr_crc02[i + 4];ptr_crc02[i + 11] = 1 - ptr_crc02[i + 11];ptr_crc02[i + 16] = 1 - ptr_crc02[i + 16];}}int res = 0;for (int i = 0; i < 16; ++i){res <<= 1;res ^= ((ptr_crc01[info_len + i] ^ ptr_crc02[info_len + i]) == ptr_hdlc[info_len + i] ? 1: 0);}return res;
}/*!* \brief deal_etc_OBU_crc 步骤03:对还原的HDLC进行校验*/
inline void deal_etc_OBU_crc()
{if (total_hdlc % 8){fprintf(stderr,"\nBad HDLC Length %d %% 8 = %d != 0\n .", total_hdlc, total_hdlc % 8);total_hdlc /= 8;total_hdlc *= 8;}if (total_hdlc < 32){fprintf(stderr,"\nHDLC Length %d < 32 \n .", total_hdlc);return;}vector<unsigned char> data;char * ptrAmp = (char *)&curr_mean;data.push_back(ptrAmp[0]);data.push_back(ptrAmp[1]);data.push_back(ptrAmp[2]);data.push_back(ptrAmp[3]);unsigned char cv = 0;for (int i = 0; i < total_hdlc; ++i){cv <<= 1;cv ^= ptr_hdlc[i];if ((i + 1) % 8 == 0){data.push_back(cv);cv = 0;}}const unsigned int crcres = crc_GBT7496_1987();data.push_back((crcres >> 8) & 0xFF);data.push_back(crcres & 0xFF);buf_resuls.push_back(data);
}/*!* \brief deal_etc_OBU_deHDLC 步骤02:对解调出的HDLC进行还原*/
inline void deal_etc_OBU_deHDLC()
{if (total_bits < 65)return;//de-hdlcbool start = false;total_hdlc = 0;int one_cnt = 0;for (int i = 16; i < total_bits; ++i){if (!start){if (ptr_bits[i] == 1)++one_cnt;else if (one_cnt == 6){start = true;one_cnt = 0;}elseone_cnt = 0;}else{if (ptr_bits[i] == 1){++one_cnt;ptr_hdlc[total_hdlc++] = 1;}else if (one_cnt < 5){one_cnt = 0;ptr_hdlc[total_hdlc++] = 0;}else if (one_cnt == 5)one_cnt = 0;else{ptr_hdlc[total_hdlc++] = 0;start = false;total_hdlc -= 8;deal_etc_OBU_crc();total_hdlc = 0;break;}}}
}} // namespace DE_ETC_OBU_CLASS_A
/*!* \brief append_etc_OBU_ClassA_20Miq 步骤01:实现ETC门框发送的波形的检测和解调。* \param data   输入IQ路数据* \param points 本波次数据长度*/
int append_etc_OBU_ClassA_20Miq(const short data[][2], const int points)
{using namespace DE_ETC_OBU_CLASS_A;if (points % down_sample){fprintf(stderr,"points must be N x down_sample points.\n");return 0;}//无滤波降采样频谱折叠,不影响两个频率的同时检测。ASK调幅不怕倒谱for (int i = 0; i < points; i += down_sample){const int pos = clk % detWin;//左统计窗口刷新sum[0] -= ptrJudge[0][pos];ptrJudge[0][pos] = ptrJudge[1][pos];sum[0] += ptrJudge[0][pos];//右统计窗口刷新sum[1] -= ptrJudge[1][pos];//AM解调ptrJudge[1][pos] = sqrt(data[i][0] * data[i][0] + data[i][1] * data[i][1])+.5;sum[1] += ptrJudge[1][pos];//检测曼彻斯特编码if (clk == next_judge){//动态均值const long long mean_half = (sum[0] + sum[1]) / detWin / 4;int c0 = true, c1 = true;for (int j = 0; j < detWin && (c1 || c0); ++j){//左侧都小于均值,右侧大于均值,是0c0 = c0 && ptrJudge[0][j] < mean_half;c0 = c0 && ptrJudge[1][j] >= mean_half;//左侧都大于均值,右侧小于均值,是1c1 = c1 && ptrJudge[0][j] >= mean_half;c1 = c1 && ptrJudge[1][j] < mean_half;}//是0if (c0){curr_mean = mean_half * 2;next_judge = clk + stepWin;lost_cnt = 0;//记录去差分后的结果ptr_bits[total_bits++] = old_bit == 0 ? 0 : 1;old_bit = 0;if (total_bits >= 65536){deal_etc_OBU_deHDLC();total_bits = 0;}}else if (c1) //是1{curr_mean = mean_half * 2;next_judge = clk + stepWin;lost_cnt = 0;//记录去差分后的结果ptr_bits[total_bits++] = old_bit == 1 ? 0 : 1;old_bit = 1;if (total_bits >= 65536){deal_etc_OBU_deHDLC();total_bits = 0;}				}else //失锁检测{next_judge = clk + 1;++lost_cnt;//窗口达到,失锁了。if (lost_cnt > detWin * 2){//Burst Over,处理。if (total_bits >= 64){deal_etc_OBU_deHDLC();}total_bits = 0;}}}++clk;}return buf_resuls.size();
}std::list<std::vector<unsigned char> > pop_etc_OBU_ClassA_20Miq()
{using namespace DE_ETC_OBU_CLASS_A;return std::move(buf_resuls);
}} // namespace DE_ETC

下一篇文章,我们要把这些逻辑和 taskBus 平台结合起来,实现实时的连续处理。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/bicheng/93842.shtml
繁体地址,请注明出处:http://hk.pswp.cn/bicheng/93842.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Spark 运行流程核心组件(二)任务调度

1、调度策略参数默认值说明spark.scheduler.modeFIFO调度策略&#xff08;FIFO/FAIR&#xff09;spark.locality.wait3s本地性降级等待时间spark.locality.wait.processspark.locality.waitPROCESS_LOCAL 等待时间spark.locality.wait.nodespark.locality.waitNODE_LOCAL 等待时…

Orbbec---setBoolProperty 快捷配置设备行为

在奥比中光&#xff08;Orbbec&#xff09;SDK&#xff08;通常称为ob库&#xff09;中&#xff0c;setBoolProperty函数是用于设置设备或传感器的布尔类型属性的核心接口。它主要用于开启/关闭设备的某些功能或模式&#xff0c;是配置设备行为的重要方法。 函数原型与参数解析…

[OWASP]智能体应用安全保障指南

1.关键组件定义 KC1 生成式语言模型&#xff08;Generative Language Models&#xff09; KC1.1 大语言模型&#xff08;LLMs&#xff09;&#xff1a;作为代理的“大脑”&#xff0c;基于预训练基础模型&#xff08;如 GPT 系列、Claude、Llama、Gemini&#xff09;&#xff…

【Vivado TCL 教程】从零开始掌握 Xilinx Vivado TCL 脚本编程(三)

【Vivado TCL 教程】从零开始掌握 Xilinx Vivado TCL 脚本编程&#xff08;三&#xff09; 系列文章目录 1、VMware Workstation Pro安装指南&#xff1a;详细步骤与配置选项说明 2、VMware 下 Ubuntu 操作系统下载与安装指南 3、基于 Ubuntu 的 Linux 系统中 Vivado 2020.1 下…

AI与大数据驱动下的食堂采购系统源码:供应链管理平台的未来发展

在数字化浪潮不断加速的今天&#xff0c;很多企业和机构都在追求一个目标&#xff1a;如何把“效率”与“成本”做到最佳平衡。对于学校、企事业单位的食堂来说&#xff0c;采购环节就是重中之重。往小了说&#xff0c;它关系到食堂员工的工作体验&#xff1b;往大了说&#xf…

HarmonyOS 实战:学会在鸿蒙中使用第三方 JavaScript 库(附完整 Demo)

摘要 在鸿蒙&#xff08;HarmonyOS NEXT / ArkTS&#xff09;开发中&#xff0c;我们大部分业务逻辑和 UI 都是用 ArkTS 写的。不过在做一些数据处理、网络请求、工具函数或者复杂算法时&#xff0c;完全没必要“重复造轮子”。这时候就可以直接引入 JavaScript 的第三方库。鸿…

C++实现教务管理系统,文件操作账户密码登录(附源码)

教务管理系统项目介绍 项目概述 这是一个基于C开发的教务管理系统&#xff0c;提供了学生、教师和系统管理员三种角色的功能模块&#xff0c;实现了教务信息的录入、查询、修改和删除等基本操作。系统采用文件存储方式保存数据&#xff0c;具有简单易用、功能完备的特点。 项…

《C++进阶之STL》【二叉搜索树】

【二叉搜索树】目录前言&#xff1a;------------概念介绍------------1. 什么是二叉搜索树?2. 二叉搜索树的性能怎么样&#xff1f;------------基本操作------------一、查找操作思想步骤简述二、插入操作目标步骤简述三、删除操作目标步骤简述------------代码实现--------…

Orange的运维学习日记--47.Ansible进阶之异步处理

Orange的运维学习日记–47.Ansible进阶之异步处理 文章目录Orange的运维学习日记--47.Ansible进阶之异步处理Playbook 执行顺序原理可选执行策略调整并发连接数&#xff1a;forks 参数查看与修改 forks性能调优建议分批执行全局任务&#xff1a;serial 关键字serial 用法示例应…

从一个ctf题中学到的多种php disable_functions bypass 姿势

题目介绍 题目是Lilctf2025 的php-jail-is-my-cry 比赛链接&#xff1a;https://lilctf.xinshi.fun/ 题目环境前半部分是 php最近的phar 新 trick 大佬的原理分析 https://fushuling.com/index.php/2025/07/30/%e5%bd%93include%e9%82%82%e9%80%85phar-deadsecctf2025-baby-we…

从繁琐到优雅:Java Lambda 表达式全解析与实战指南

在 Java 8 之前&#xff0c;我们习惯了用匿名内部类处理回调、排序等场景&#xff0c;代码中充斥着大量模板化的冗余代码。直到 Java 8 引入 Lambda 表达式&#xff0c;这一局面才得以彻底改变。作为一名深耕 Java 多年的技术专家&#xff0c;我见证了 Lambda 表达式如何从一个…

《当 AI 学会 “思考”:大语言模型的逻辑能力进化与隐忧》

引言&#xff1a;AI “思考” 的时代信号​大语言模型展现逻辑能力的典型场景&#xff1a;如复杂问题推理、多步骤任务规划的实例&#xff08;如 AI 辅助撰写科研思路、进行案件逻辑梳理等&#xff09;​提出核心议题&#xff1a;大语言模型逻辑能力的进化究竟达到了怎样的程度…

企业知识管理革命:RAG系统在大型组织中的落地实践

企业知识管理革命&#xff1a;RAG系统在大型组织中的落地实践 &#x1f31f; Hello&#xff0c;我是摘星&#xff01; &#x1f308; 在彩虹般绚烂的技术栈中&#xff0c;我是那个永不停歇的色彩收集者。 &#x1f98b; 每一个优化都是我培育的花朵&#xff0c;每一个特性都是我…

MySQL事务篇-事务概念、并发事务问题、隔离级别

事务事务是一组不可分割的操作集合&#xff0c;这些操作要么同时成功提交&#xff0c;要么同时失败回滚。acid事物的四大特性原子性最小工作单元&#xff0c;要么同时成功&#xff0c;要么同时失败。例如A转账300给B,A账户-300与B账户300必须满足操作原子性&#xff0c;避免出现…

C++高频知识点(二十三)

文章目录111. 谈谈atomic1. 什么是原子操作&#xff1f;2. std::atomic 的基本使用示例&#xff1a;基本使用3. 原子操作方法4. 内存模型与顺序一致性112. 引用成员变量是否占空间?1. 引用成员变量的定义2. 内存占用情况1. 成员变量的实际占用2. 类的总大小代码分析113. C中深…

云存储的高效安全助手:阿里云国际站 OSS

在这个数据爆炸的时代&#xff0c;数据存储和管理成为了众多企业和个人面临的一大挑战。想象一下&#xff0c;你是一位视频博主&#xff0c;随着粉丝量的增长&#xff0c;视频素材越来越多&#xff0c;电脑硬盘根本装不下&#xff0c;每次找素材都要花费大量时间。又或者你是一…

【线性基】P4301 [CQOI2013] 新Nim游戏|省选-

本文涉及知识点 C贪心 位运算、状态压缩、枚举子集汇总 线性基 P4301 [CQOI2013] 新Nim游戏 题目描述 传统的 Nim 游戏是这样的&#xff1a;有一些火柴堆&#xff0c;每堆都有若干根火柴&#xff08;不同堆的火柴数量可以不同&#xff09;。两个游戏者轮流操作&#xff0c;…

[25-cv-09610]Anderson Design Group 版权维权再出击,12 张涉案图片及近 50 个注册版权需重点排查!

Anderson 版权图案件号&#xff1a;25-cv-09610立案时间&#xff1a;2025年8月13日原告&#xff1a;Anderson Design Group, Inc.代理律所&#xff1a;Keith原告介绍原告是美国的创意设计公司&#xff0c;成立于1993年&#xff0c;简称ADG&#xff0c;一家家族企业&#xff0c;…

Mac下载AOSP源代码

一、前期准备 硬件要求 至少 200GB 可用空间(源码约 100GB,编译产物需额外空间),推荐 SSD。 内存 16GB+,避免同步 / 编译时卡顿。 系统要求 macOS 10.14+(推荐最新版本,兼容性更好) 二、环境配置 AOSP 源码包含大小写不同的文件(如 File.java 和 file.java),而 …

Linux之网络

Linux之网络两个模型应用层协议HTTPS传输层协议UDPTCP可靠性与效率的兼顾面向字节流TCP异常情况底层实现网络层协议IP网段划分子网划分NAT数据链路层协议以太网ARP代理服务器内网穿透五种IO多路复用Reactor模式本文旨在讲解tcp-ip协议原理&#xff0c;并不涉及代码部分&#xf…