上一篇文章原理以及发射代码看得差不多了。接收机代码由于本来实现就不完整,不用多看,边改代码边理解。
下面是我改得差不多了的代码,已经可以做到把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数据相比较。如果想当就算校验通过。
演示视频: