Portapack应用开发教程(十五) APRS接收 B

上一篇文章原理以及发射代码看得差不多了。接收机代码由于本来实现就不完整,不用多看,边改代码边理解。

下面是我改得差不多了的代码,已经可以做到把aprs发射程序发的数据在afsk接收程序里显示了。

我的核心思想是,绕开所有跟编码解码有关的东西,着重确保调制解调ok。

这样的话那些什么一串111之间加个0之类的算法我就去掉了。另外本来是0要变频,1不变频,到达底层的时候重新编码为1是2200Hz恒定,0是1200Hz恒定。我把这部分编码也绕开了,我输入的比特数据直接决定了频率,如果比特是1就是2200Hz,0就是1200Hz。

所以,我在ax25.cpp里加入了add_byte_direct函数。

不但如此,我在打包的函数里直接发了80个0xAA作为包头标志,然后发了一连串(16个)有特殊意义好区分出来的数据。

ax25.cpp

void AX25Frame::add_byte_direct(uint8_t byte) {
	*bb_data_ptr = byte;
	bb_data_ptr++;
}



void AX25Frame::make_ui_frame(char * const address, const uint8_t control,
	const uint8_t protocol, const std::string& info) {
	
	size_t i;
	
	bb_data_ptr = (uint16_t*)shared_memory.bb_data.data;
	memset(bb_data_ptr, 0, sizeof(shared_memory.bb_data.data));
	bit_counter = 0;
	current_bit = 0;
	current_byte = 0;
	ones_counter = 0;
	crc_ccitt.reset();

	for (uint32_t i = 0; i < 80; i++) 
	{
		add_byte_direct(0xAA);
	}
	add_byte_direct(0x01);
	add_byte_direct(0x02);
	add_byte_direct(0x03);
	add_byte_direct(0x04);
	add_byte_direct(0x05);
	add_byte_direct(0x06);
	add_byte_direct(0x07);
	add_byte_direct(0x08);
	add_byte_direct(0x09);
	add_byte_direct(0x0A);
	add_byte_direct(0x0B);
	add_byte_direct(0x0C);
	add_byte_direct(0x0D);
	add_byte_direct(0x0E);
	add_byte_direct(0x0F);

	
	flush();
}

由于aprs发射本来没问题,所以它的调制部分我不动,只改它的编码部分,这个编码部分也只是为了我调试接收时方便,以后要改回来的。

接下来我要改接收的解调部分。

我加了一些注释,这个clean_transition代码如果去掉的话,就无法数出那一串AA了,可能只能数到零星几个。

下面的代码我从rs232代码里精简了一下,放到了这里,我发现要把wait_start之类的状态机去掉,要不然AA数量总比实际的少。 

proc_afskrx.cpp execute()

// Check for "clean" transition: either 0011 or 1100
		//because originally in transmission we use 1200Hz to represent 1, and 2200Hz to represent 0
		//but 1200Hz sin wave has a lot of sample points
		//here the sample_bits is demodulated result, if sample points is at 1200Hz, (sample_bits & 1) are all 111111
		//so we actually obtained around 20 (sample_bits & 1) after demodulation for one bit.
		//if we want to make sure the result is 1, we want to get a sequence of 20 1, 
		//so at least one sample_bits as a byte should be 11111111, if not it's in transition period. We should do a bit of jump
		//That's what happening below. 0x8000 is a half of 0x10000, it's a threshold whether jump to right or left.

		if ((((sample_bits >> 2) ^ sample_bits) & 3) == 3) {
			// Adjust phase
			if (phase < 0x8000)
				phase += 0x800;		// Is this a proper value ?
			else
				phase -= 0x800;
		}
		
		phase += phase_inc;  

		//phase_inc is 0x10000/(audio_fs/baud),  this audio_fs/baud = 20. So every 20 audio samples stands for the same bit
		//we need to count to 20 for another bit. if phase_inc is 1, then if phase>=20, we obtain one bit.
		//now in initialization, we multiply phase_inc with 0x10000/20
		//as a result, we should multiply the comparison too
		//so if phase >= 20 *(0x10000/20), that is if phase >= 0x10000, we can obtain new bit.
		
		if (phase >= 0x10000) {
			phase &= 0xFFFF; // if phase == 0x10000, after the & 0xFFFF, phase becomes 0
			trigger_word = true;
			if (trigger_word) {
				word_bits <<= 1;
				word_bits |= (sample_bits & 1);
					
				bit_counter++;

				if (bit_counter == word_length) {
					bit_counter = 0;
					
					data_message.is_data = true;
					data_message.value = word_bits;
					shared_memory.application_queue.push(data_message);
				}
				
				
			} else {
				
				// RS232-like modem mode
				if (state == WAIT_START) {
					if (!(sample_bits & 1)) {
						// Got start bit
						state = RECEIVE;
						bit_counter = 0;
					}
				} else if (state == WAIT_STOP) {
					if (sample_bits & 1) {
						// Got stop bit
						state = WAIT_START;
					}
				} else {
					word_bits <<= 1;
					word_bits |= (sample_bits & 1);
					
					bit_counter++;
				}
				
				if (bit_counter == word_length) {
					bit_counter = 0;
					state = WAIT_STOP;
					
					data_message.is_data = true;
					data_message.value = word_bits;
					shared_memory.application_queue.push(data_message);
				}
				
			}
		}

接下来是afsk接收的上层代码,一旦数到足够数量的AA,就从第一个不是AA的数据开始打印8个数据出来,看看是否和发射机发出来的相符。 

ui_afsk_rx.cpp on_data()

if (print_count > 0) 
		{
			str_console += "[" +  to_string_hex(value, 2) + "]";	// Not printable
			console.write(str_console);
			print_count--;
		}
		if (value == 0xAA)
		{
			flag_count++;
		}
		if ((value != 0xAA) && (prev_value == 0xAA)) 
		{
			if (flag_count > 50)
			{
				//console.writeln("");
				console.write("[" + to_string_dec_int(flag_count, 2) + "]");
				flag_count = 0;
				print_count = 8;
				//console.writeln("");
			}

		}
		prev_value = value;

结果如上图。

可以看到白色里的是数AA的结果80左右和实际发的差不多。

但是AA后跟的数据应该是[01][02][03][04][05][06][07][08]

实际并不是,但是这些都是有规律的,是8位bit的分组出现了问题,导致转16进制的时候出错了。我们可以仔细看看。

[10][20][30][40][50][60][70][80]

[40][80][C1][01][41][81][C2][02]

[04][08][0C][10][14][18][1C][20]

[02][03][04][05][06][07][08][09]

这是图片上能得到的数据,我们接下来归一下类。

最后那一行数据其实可能是第一个紧跟[AA]的没打印出来,如果打出来的话,它就是[01]~[08]跟我发射的完全相符。

除此以外[10]~[80]与我的[01]~[08]差距也不大,应该就是低4位被搞错了被左移到高4位上去了。

接下来[40][80]~[C2][02]与[04][08]~[1C][20]应该也是一类数据,它们之间的差别也是低4位被左移4位到了高4位上。

那么[04][08]~[1C][20]与[01][02]~[07][08]到底有什么关系,它们会不会是从同一组数据衍生出来的呢。答案是肯定的。

[04] 0000 0100        [01] 0000 0001

[08] 0000 1000        [02] 0000 0010

可以看到这两组数据开头的两个结果比较一下的话 其实是低4位被左移了2位。

后面几个数据结果也都左移2位就都能对应起来了。

所以我们可以得出结论,我每次发射,接收机都能算出差不多OK的数据。

只不过这个数据可能被左移2位,4位,6位(先左移2位再左移4位)。

我们接下来要做的就是搞清楚每个数据到底从哪里开始,这样就不会搞错了

后来我发现包头[AA]是10101010,它本来就有问题,没法区分如果左移2 4 6位得到的结果还是本身,所以导致了包头后面第一个数据找错开始位置。我换为0x7E就解决了这个问题。如下图:

可以看到数据都是准确的了。

但是还有别的问题,一个是包头太长了,如果都需要发80那么长的包头肯定效率不高。而且也不是我每次点发射都能出现数据。

我又尝试改了代码,我发现proc_afsk_rx的trigger_word如果是false就是按一下出一条,缺点是会出现错误的报文(移位问题)。如果是true,只要出现报文就是正确的,但是缺点是按下后不一定出结果(因为80个7E可能全被接收为CF之类的了)。

我后来想了想,不打算用false,因为他是用rs232在做同步。如果是true就是什么都没做,直接把解调完了的数据给到上层ui代码。至于为啥按下不一定有结果,是因为7E被移位了。

这个移位的判断我不打算在proc里做,proc里只做解调就行。移位处理跟解码放到一起都在ui离做。

下面就是解码。经过多次尝试,我发现7E大多数时候经过调制以后都变成FE,而不是01。然后是FE又被移位,导致可能没法探测出来。

FE是有特点的11111110,我们把它取反后得到00000001,如果这个数据移位,那么其实得到的是在各个位置的one hot数据。类似01000000, 00010000等。它们规律很明显。通过这个值对应的16进制数,就可以知道移了多少位move_bit。

用包头计算的移位结果去修正后面收到的数据,那么后面的数据就都是准去的了。

如果在后续30个数据以内收到包尾,就算是有效包,就可以把中间的数据解码或者直接打印出来了。


void AFSKRxView::on_data(uint32_t value, bool is_data) {
	std::string str_console = "\x1B";
	std::string str_byte = "";
	
	if (is_data) {
		// Colorize differently after message splits
		str_console += (char)((console_color & 3) + 9);

		value &= 0xFF;
        //if we directly send 7E, instead of encoding it to FE
		//we'll find flag is always F3 CF F9 9F FC 3F
		//they are all like 7E, but never be 7E
		//1. we can shift it manually here
		//2. If we add encoding and decoding, the flag 7E is already encoded to 11111110 before sending to air, but since we can get 7E back, the decode is partially working

		if (status_check == false) 
		{

			if (value == prev_value)
			{
				flag_count++;
			}
			if (value != prev_value) 
			{
				if (flag_count > 2)
				{
					if (prev_value == 0x01 || prev_value == 0x02 || prev_value == 0x04 || prev_value == 0x08 || prev_value == 0x10 || prev_value == 0x20 || prev_value == 0x40 || prev_value == 0x80)
					{
						compare_value = prev_value;
					}
					else
					{
						compare_value = ~prev_value;
					}
					if (compare_value == 0x01)
					{
						move_bit = 0;
					}
					else if  (compare_value == 0x02)
					{
						move_bit = 1;
					}
					else if  (compare_value == 0x04)
					{
						move_bit = 2;
					}
					else if  (compare_value == 0x08)
					{
						move_bit = 3;
					}
					else if  (compare_value == 0x10)
					{
						move_bit = 4;
					}
					else if  (compare_value == 0x20)
					{
						move_bit = 5;
					}
					else if  (compare_value == 0x40)
					{
						move_bit = 6;
					}
					else if  (compare_value == 0x80)
					{
						move_bit = 7;
					}

					if (compare_value >> move_bit == 0x01)
					{
						status_check = true;
						str_console += "start: [" + to_string_dec_int(move_bit, 2) + "]";	
						console.write(str_console);
						flag_count = 0;
						frame_counter = 0;
					}
					else
					{
						flag_count = 0;
						move_bit = 0;
					}
				}
			}
		}
		combine_prev_curr_value = (prev_value << 8) | value;
		adjusted_current_value = (combine_prev_curr_value >> move_bit) & 0xFF;

		if (status_check == true)
		{
			frame_counter++;
			if (frame_counter < 30)
			{
				frame[frame_counter] = adjusted_current_value;
				if ((adjusted_current_value == 0xFE || adjusted_current_value == 0x01) && frame_counter > 1)
				{
					frame_length = frame_counter;
					str_console += "length:[" +  to_string_dec_int(frame_counter, 2) + "]";	
					console.write(str_console);
					console.writeln("");
					//decode_frame();
                    console.write("[" + to_string_hex(frame[frame_counter], 2) + "]");
					console.writeln("");
					status_check = false;
					frame_counter = 0;
					last_bit = 0;
					
				}
				else if ((adjusted_current_value == 0xFE || adjusted_current_value == 0x01) && frame_counter == 1)
				{
					frame_counter = 0;
				}
				
			}
			else
			{
				console.write("false_detect");
				console.writeln("");
				frame_counter = 0;
				status_check = false;
			}
			
		}



		prev_value = value;

	} else {
		// Baudrate estimation
		text_debug.set("~" + to_string_dec_uint(value));
	}
}

这是实验效果:

https://www.bilibili.com/video/BV1iz4y1r7s4/ 

至于为啥刚刚寻找包头时compare_value有时候就是prev_value有时候是~prev_value要取反。这个我们后面才有必要用到。

现在确认了我aprs rx能直接解调tx发出的未编码的数据。接下来我要开始编码和解码了。

 

要按照aprs的要求来打包了,就不能直接这么发16进制数据了。

对于包头来说,区别就是本来直接发01111110,变为发00000001,11111110。

为什么会得到00000001,和11111110,是因为实际要经过NRZI调制,0是要变频的,1不变频。

那么如果本来是1对应的2200Hz,数据位是0,就要变为0对应的1200Hz,如果是0对应的1200Hz就要变为1对应的2200Hz。

如果数据位是1,那么频率不变,对应数字就不变。

所以本来中间一长串的6个1对应的NRZI调制后的数据就是维持不变,只在开头末尾有变化。

 

首先,我不考虑bit stuffing而是直接用0表示01切换,1表示01不变。

这时候我得用add_data代替add_byte_direct。它会把数据进行编码。把编码后的数据发射出去。我故意选了一些没有连续好多1的,这样不会触发bit_stuffing。


void AX25Frame::make_ui_frame(char * const address, const uint8_t control,
	const uint8_t protocol, const std::string& info) {
	
	size_t i;
	
	bb_data_ptr = (uint16_t*)shared_memory.bb_data.data;
	memset(bb_data_ptr, 0, sizeof(shared_memory.bb_data.data));
	bit_counter = 0;
	current_bit = 0;
	current_byte = 0;
	ones_counter = 0;
	crc_ccitt.reset();

	add_flag();
	add_flag();
	add_flag();
	add_flag();

	add_data(0xA1);
	add_data(0xA1);
	add_data(0xA1);
	add_data(0xB2);
	add_data(0xC3);
	add_data(0x21);
	add_data(0xD4);
	add_data(0xF6);

	for (i = 0; i < info.size(); i++)
		add_data(info[i]);

	add_flag();
	add_flag();

	/*
	
	make_extended_field(address, 14);
	add_data(control);
	add_data(protocol);
	
	for (i = 0; i < info.size(); i++)
		add_data(info[i]);
	
	add_checksum();
	
	*/
	
	flush();

下面是解码程序,把之前的接收UI里的直接print的位置换成这个解码函数就能解出数据 。


void AFSKRxView::decode_frame()
{

	for (int i = 1; i < frame_length; i++)
	{
		for (int j = 7; j >=0; j--)
		{
			current_bit = (frame[i] >> j) & 1;
			frequency_change = current_bit ^ last_bit;

			current_byte <<= 1;
			current_byte |= !frequency_change;

			last_bit = current_bit;
		}
		//LSB->MSB
		current_byte = ((current_byte & 0xF0) >> 4) | ((current_byte & 0x0F) << 4);	// EFGHABCD
		current_byte = ((current_byte & 0xCC) >> 2) | ((current_byte & 0x33) << 2);	// GHEFCDAB
		current_byte = ((current_byte & 0xAA) >> 1) | ((current_byte & 0x55) << 1);	// HGFEDCBA
		current_byte &= 0xFF;			
		console.write("[" + to_string_hex(current_byte, 2) + "]");

	}
}

当时我发现了一个新的问题,不是所有add_data里的数据都能成功解出来,如果这个数据是B2之类的就没问题,如果是A1就不行。

我后来试下来发现如果add_data(0xA1)那么flag就会变为0x01而不是0xFE了,所以我要考虑所有前后flag都可能是0x01和0xFE的两种情况。这就是说为啥我前面compare_value的取值是prev_value取反和不取反都要检测。在包尾探测的时候,也是如此0x01和0xFE两种可能都可能是包尾。

 

上面贴的代码已经把这些问题解决好了,我没贴解码0xA1会出问题的代码。


接下来要考虑bit_stuffing。

这个问题比较麻烦,它会在编码前的数据里增加0,会导致包长变化。

这样一来,可能原来通过包头找出的move_bit,到包尾这里就不准了。我可以计数,出现几次bit_stuffing就加几个move_bit。

但是这样做还是有问题。因为我每次都得探测包尾成功后才去decode_frame。而我对bit_stuffing的计数,最方便的也是在decode_frame里做。

如果包尾没找到,我根本就不会decode,但是不decode我又没法找出准确的包尾。这样就陷入死循环了。

我考虑把寻找包尾的判别标准改一下。

之前是如果包尾连续出现0xFE或者0x01就算包尾。现在条件要放宽。跟找包头类似,连续出现多个相同的数据,并且经过取反或者不取反都能变为one_hot就算找到包尾了。然后再去慢慢decode_frame就行。

 

我把bit_stuffing已经解决了。这样就能解出payload里的数据了。

接下来我要把其他几个field都加进去。我一开始加了比较简单的control和protocol。因为它们实际只是2个byte。但是我发现只加control 0x03没问题,加入protocol 0xF0就不行了。后来我发现这是因为我的测试信号源也就是aprs tx程序本身有bug。

按照这个来改就行。应该是发射时的bit_stuffing部分有问题,溢出了。 

https://github.com/eried/portapack-mayhem/pull/213/commits/95f7eda9c536d90601056689cacbc6c6340a8ddc

这个问题解决了以后,我把address相关的14个byte也加入了,解出来也没多少难度。

最后我加入了crc校验。就是把从无线电里收到的crc数据和本地运算的crc数据相比较。如果想当就算校验通过。 

演示视频:

https://www.bilibili.com/video/BV1nA411W7cC

已标记关键词 清除标记
©️2020 CSDN 皮肤主题: 设计师:老邵的开源世界 返回首页