武汉大学卫星导航算法程序设计——解码与数据获取

还在为解码发愁吗?面对二进制文件还是无从下手吗?一篇文章帮你搞定。

我们从接收机获取的数据并不是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;
}

本次内容就分享到这里了,大家有什么疑问欢迎和我交流。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

梦想是造卫星

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值