还在为解码发愁吗?面对二进制文件还是无从下手吗?一篇文章帮你搞定。
我们从接收机获取的数据并不是rinex格式的文件,而是NovAtel数据格式的二进制文件。我们需要从文件中提取出我们需要的导航数据,也就是解码的过程。废话不多说,我们直接开始讲解。
一、Binary数据头格式
请不要使用文本文件打开二进制文件,会显示乱码。使用Binary Viewer软件可以打开二进制的文件,大概长下面这个样子。但是我们不需要读懂这个文件。
AA,44,12是三个同步符,读取到这三个同步符代表同步到了消息,就继续往下面读。
匹配同步符的函数如下:
/*匹配同步符的函数*/
static int sync_oem4(uint8_t *buff, uint8_t data)
{
buff[0] = buff[1]; buff[1] = buff[2]; buff[2] = data;
return buff[0] == OEM4SYNC1 && buff[1] == OEM4SYNC2 && buff[2] == OEM4SYNC3;
}
在数据头里,我们需要以下数据
第五列为每个数据所占的字节,按照字节数提取可。因为我们需要提取不同的数据,有的为整数,有的为浮点数,所以我们先定义一下提取不同类型数据的函数。代码如下:
/*解码辅助函数,用于获取不同类型的数据*/
class DataReader
{
public:
//读取一个无符号8位整数
static uint8_t U1(const uint8_t *p)
{
return *p;
}
// 获取一个有符号8位整数
static int8_t I1(const uint8_t *p)
{
return *reinterpret_cast<const int8_t *>(p);
}
// 获取一个无符号16位整数 (小端)
static uint16_t U2(const uint8_t *p)
{
uint16_t u;
memcpy(&u, p, sizeof(u));//复制字节(第一个参数为目标空间的地址,第二个为赋值字节的地方,第三个参数为复制字节的大小)
return u;
}
// 获取一个无符号32位整数 (小端)
static uint32_t U4(const uint8_t *p)
{
uint32_t u;
memcpy(&u, p, sizeof(u));
return u;
}
// 获取一个有符号32位整数 (小端)
static int32_t I4(const uint8_t *p)
{
int32_t i;
memcpy(&i, p, sizeof(i));
return i;
}
// 获取一个单精度浮点数 (小端)
static float R4(const uint8_t *p)
{
float r;
memcpy(&r, p, sizeof(r));
return r;
}
// 获取一个双精度浮点数 (小端)
static double R8(const uint8_t *p)
{
double r;
memcpy(&r, p, sizeof(r));
return r;
}
};
DataReader DR;
然后我们就可以开始提取数据了。
/*获取当前解码的历元,判断消息的类型,转到不同的解码函数*/
/*------------------二进制数据解码函数-----------------*/
static int decode_oem4(raw_data *raw)
{
double tow;
int msg, stat, week;
int type = DR.U2(raw->buff + 4);//从第四个字节开始读取一个16位的整数,即为数据的类型编码...
raw->type = type;
/* crc校验码检查 */
if (crc32(raw->buff, raw->len) != DR.U4(raw->buff + raw->len))
{
std::cout << "oem4 crc error: type=" << type << " len=" << raw->len << std::endl;
return -1;
}
msg = (DR.U1(raw->buff + 6) >> 4) & 0x3; /* 消息的种类: 00=binary,01=ascii */
stat = DR.U1(raw->buff + 13);//时间的状态
week = DR.U2(raw->buff + 14);//参考历元的周数
/*时间状态和周数检验,stat的值为0-23*/
if (stat == 20 || week == 0)
{
cout << "oem4 time error: type=" << type << " msg=" << msg << " stat=" << stat << " week=" << week << endl;
return 0;
}
//week = adjgpsweek(week);//改正周数
tow = DR.U4(raw->buff + 16)*0.001;//GPS参考周秒(将ms单位转换成s)
raw->time = gpst2time(week, tow);//转换为时间戳
Commontime C;
time2Commontime(raw->time, C);
cout << ">" << " " << C.Year << " " << C.Month << " " << C.Day << " " << C.Hour << " " << C.Minute << " " << C.Second << endl;
cout << type << endl;
/*检查是否为我们关注的二进制数据*/
if (msg != 0)
{
return 0;
}
/*判断需要解码的数据类型*/
switch (type)
{
case ID_RANGE:
return decode_rangeb(raw);
case ID_GPSEPHEM:
return decode_gpsephem(raw);
case ID_BDSEPHEMERIS:
return decode_bdsephemerisb(raw);
}
return 0;
}
crc32为校验码函数,用来检查数据在通信过程中是否存在错误,代码如下:
unsigned int crc32(const unsigned char *buff, int len)
{
int i, j;
unsigned int crc = 0;
for (i = 0; i < len; i++)
{
crc ^= buff[i];
for (j = 0; j < 8; j++)
{
if (crc & 1) crc = (crc >> 1) ^ POLYCRC32;
else crc >>= 1;
}
}
return crc;
}
接下来就是解不同类型的消息文件了。我们要求是解RANGE,GPSEPHEM,BDSEPHEMERIS三种类型的文件。对应的消息编码为43,7,1696。
注意,在rtklib里面没有解GPSEPHEM的函数。
二、星历及观测值函数
range格式:
GPSEPHEM格式:
BDSEPHEMERIS格式:
大家也可以自行查阅oem4的官方文档,会有以上说明。
解观测值代码:
static int decode_rangeb(raw_data *raw)
{
double psr, adr, dop, snr, lockt, tt;/*伪距,相位,多普勒频移,载噪比,锁定时间,时间差*/
char *msg;
int i,index,nobs, prn, sat, sys, code, freq, pos;
int track, plock, clock, parity, halfc, lli, gfrq;
int number_sys;
unsigned char *p = raw->buff + OEM4HLEN;//开始截取数据的位置
cout<<"decode_rangeb: len="<<raw->len<<endl;
nobs = DR.U4(p);//截取卫星观测数量
if (raw->len < OEM4HLEN + 4 + nobs * 44)//每个卫星的观测记录为44个字节
{
cout << "oem4 rangeb length error: len=" << raw->len << " nobs=" << nobs << endl;
}//检查消息的长度是否包含观测数据
/* p移动4个字节为第一个数据的位置,每44个字节为一个卫星的观测记录,循环读取数据*/
for (i = 0, p += 4; i < nobs; i++, p += 44)
{
/* 解码卫星跟踪状态,返回值为0或1,0为第一个频点,1为第二个频点 */
pos = decode_trackstat(DR.U4(p + 40), &sys, &code, &track, &plock, &clock,&parity, &halfc,number_sys);
if (pos < 0)
{
continue;
}
prn = DR.U2(p);//截取卫星的PRN号
sat = satno(sys, prn);//对PRN号进行判断
if (sat <= 0)
{
continue;
}
gfrq = DR.U2(p + 2);
psr = DR.R8(p + 4);//伪距
adr = DR.R8(p + 16);//相位
dop = DR.R4(p + 28);//多普勒观测值
snr = DR.R4(p + 32);//观测噪声
lockt = DR.R4(p + 36);//锁定时间
/*-----周跳检测-----*/
if (raw->tobs[sat - 1][pos].time != 0)
{
tt = timediff(raw->time, raw->tobs[sat - 1][pos]);
lli = lockt - raw->lockt[sat - 1][pos] + 0.05 <= tt ? LLI_SLIP : 0;
}
else
{
lli = 0;
}
raw->tobs[sat - 1][pos] = raw->time;
raw->lockt[sat - 1][pos] = lockt;
raw->halfc[sat - 1][pos] = halfc;
if (!clock) psr = 0.0; /* code unlock */
if (!plock) adr = dop = 0.0; /* phase unlock */
if (fabs(timediff(raw->obs.data[0].time, raw->time)) > 1E-9)
{
raw->obs.n = 0;
}
if ((index = obsindex(&raw->obs, raw->time, sat,number_sys)) >= 0)
{
raw->obs.data[index].L[pos] = -adr;
raw->obs.data[index].P[pos] = psr;
raw->obs.data[index].D[pos] = (float)dop;
raw->obs.data[index].SNR[pos] =
0.0 <= snr && snr < 255.0 ? (unsigned char)(snr*4.0 + 0.5) : 0;
raw->obs.data[index].LLI[pos] = (unsigned char)lli;
raw->obs.data[index].code[pos] = code;
raw->obs.data[index].sat = sat;
raw->obs.data[index].num_sys = number_sys;
raw->obs.data[index].time = raw->time;
}
}
save_obsfile(raw);//输出到文件
return 1;
}
星历按照同样的方法读即可,我就不放代码了。
三、读文件和主函数
由于我们是从文本文件获取的数据,所以我们还有一个读文件的函数。
int readfile(ifstream &fp,raw_data *raw)
{
if (raw->nbyte == 0)
{
for (int i = 0; ; i++)
{
if ((data = fp.get()) == EOF)
{
return -2; // 读取到文件结束符
}
if (sync_oem4(raw->buff, static_cast<uint8_t>(data)))
{
break;//匹配到同步符则退出循环
}
if (i >= 4096)
{
return 0;
}//如果循环次数超过4096,说明同步失败
}
}
// 从文件流读取数据
fp.read(reinterpret_cast<char *>(raw->buff + 3), 7); // 读取7字节
raw->len = DR.U2(raw->buff + 8) + OEM4HLEN;//获取消息的长度
/* 读取有效数据 */
fp.read(reinterpret_cast<char *>(raw->buff + 10), raw->len - 6);//从buff的第10个字节开始,读取(消息长度-6字节)长度的数据
raw->nbyte = 0;
/* 解码 NovAtel OEM4 信息 */
decode_oem4(raw);
return 1;
}
主函数:
int main()
{
string filename = "NovatelOEM20211114-01.log";
ifstream fp(filename, ios::binary);//以二进制格式打开文件
raw_data raw;
while (true)
{
int result = readfile(fp, &raw); // 循环调用 readfile 函数
if (result == -2)
{
std::cout << "读取到文件结束符" << std::endl;
break; // 读取结束或失败,退出循环
}
else if (result == -1)
{
std::cerr << "消息长度错误,跳过此消息" << std::endl;
continue; // 如果长度错误,跳过此消息
}
}
outfile.close();
outfile_g.close();
outfile_n.close();
fp.close();
std::cout << "Hello World!\n";
return 0;
}
本次内容就分享到这里了,大家有什么疑问欢迎和我交流。
标签:武汉大学,return,int,解码,pos,raw,程序设计,data,buff From: https://blog.csdn.net/qq_73763569/article/details/143029764