在上一篇文章中,使用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。
为了避免反复统计均值,在上述窗口中,采用时钟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);
在实时工作模式下,此模块的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 平台结合起来,实现实时的连续处理。