Everything changed.

* Rewrote preamble detector. Uses integrate-and-dump along with correlator to perform optimal receive.
* No longer using framer. Determining packet length via header. Missing some anti-FRUIT stuff.
* Pulled out the slicer logic into its own function.

Lots of todos and fixmes.
This commit is contained in:
Nick Foster 2011-06-11 21:32:28 -07:00
parent c8dce82b75
commit 8b1c1791c7
5 changed files with 182 additions and 181 deletions

View File

@ -60,8 +60,8 @@ int air_modes_framer::work(int noutput_items,
gr_vector_void_star &output_items)
{
//do things!
const float *inraw = (const float *) input_items[0];
float *outraw = (float *) output_items[0];
const float *in = (const float *) input_items[0];
float *out = (float *) output_items[0];
//unsigned char *outattrib = (unsigned char *) output_items[0];
int size = noutput_items - d_check_width*2;
if(size < 0) return 0;
@ -73,7 +73,7 @@ int air_modes_framer::work(int noutput_items,
get_tags_in_range(tags, 0, abs_sample_cnt, abs_sample_cnt + size, pmt::pmt_string_to_symbol("preamble_found"));
std::vector<pmt::pmt_t>::iterator tag_iter;
memcpy(outraw, inraw, size * sizeof(float));
memcpy(out, in, size * sizeof(float));
for(tag_iter = tags.begin(); tag_iter != tags.end(); tag_iter++) {
uint64_t i = gr_tags::get_nitems(*tag_iter) - abs_sample_cnt;
@ -81,18 +81,18 @@ int air_modes_framer::work(int noutput_items,
packet_attrib = Long_Packet;
//let's use the preamble marker to get a reference level for the packet
reference_level = (inraw[i]
+ inraw[i+int(1.0*d_samples_per_symbol)]
+ inraw[i+int(3.5*d_samples_per_symbol)]
+ inraw[i+int(4.5*d_samples_per_symbol)]) / 4;
reference_level = (in[i]
+ in[i+2]
+ in[i+7]
+ in[i+9]) / 4.0;
//armed with our reference level, let's look for marks within 3dB of the reference level in bits 57-62 (65-70, see above)
//if bits 57-62 have marks in either chip, we've got a long packet
//otherwise we have a short packet
//NOTE: you can change the default here to be short packet, and then check for a long packet. don't know which way is better.
for(int j = (65 * d_samples_per_symbol); j < (70 * d_samples_per_symbol); j += d_samples_per_symbol) {
float t_max = std::max(inraw[i+j],
inraw[i+j+d_samples_per_chip]
float t_max = std::max(in[i+j],
in[i+j+d_samples_per_chip]
);
if(t_max < (reference_level / 2.0)) packet_attrib = Short_Packet;
}
@ -114,7 +114,7 @@ int air_modes_framer::work(int noutput_items,
//insert tag here
add_item_tag(0, //stream ID
nitems_written(0)+i, //sample
d_key, //preamble_found
d_key, //frame_info
pmt::pmt_from_long((long)packet_attrib),
d_me //block src id
);

View File

@ -35,17 +35,19 @@ air_modes_preamble_sptr air_make_modes_preamble(int channel_rate, float threshol
}
air_modes_preamble::air_modes_preamble(int channel_rate, float threshold_db) :
gr_sync_block ("modes_preamble",
gr_block ("modes_preamble",
gr_make_io_signature2 (2, 2, sizeof(float), sizeof(float)), //stream 0 is received data, stream 1 is moving average for reference
gr_make_io_signature (1, 1, sizeof(float))) //the original data. we pass it out in order to use tags.
gr_make_io_signature (1, 1, sizeof(float))) //the output packets
{
d_chip_rate = 2000000; //2Mchips per second
d_samples_per_chip = channel_rate / d_chip_rate; //must be integer number of samples per chip to work
d_samples_per_symbol = d_samples_per_chip * 2;
d_check_width = 7.5 * d_samples_per_symbol; //only search to this far from the end of the stream buffer
d_check_width = 120 * d_samples_per_symbol; //only search to this far from the end of the stream buffer
d_threshold_db = threshold_db;
d_threshold = powf(10., threshold_db/10.); //the level that the sample must be above the moving average in order to qualify as a pulse
set_output_multiple(1+d_check_width*2);
std::stringstream str;
str << name() << unique_id();
d_me = pmt::pmt_string_to_symbol(str.str());
@ -59,111 +61,110 @@ static int early_late(const float *data) {
else return 0;
}
int air_modes_preamble::work(int noutput_items,
static void integrate_and_dump(float *out, const float *in, int chips, int samps_per_chip) {
for(int i=0; i<chips; i++) {
float acc = 0;
for(int j=0; j<samps_per_chip; j++) {
acc += in[i*samps_per_chip+j];
}
out[i] = acc/samps_per_chip;
}
}
//the preamble pattern in bits
//fixme goes in .h
static const bool preamble_bits[] = {1, 0, 1, 0, 0, 0, 0, 1, 0, 1};
static double correlate_preamble(const float *in, int samples_per_chip) {
double corr = 0.0;
for(int i=0; i<10; i++) {
for(int j=0; j<samples_per_chip;j++)
if(preamble_bits[i]) corr += in[i*samples_per_chip+j];
}
return corr;
}
int air_modes_preamble::general_work(int noutput_items,
gr_vector_int &ninput_items,
gr_vector_const_void_star &input_items,
gr_vector_void_star &output_items)
{
//do things!
const float *in = (const float *) input_items[0];
const float *inavg = (const float *) input_items[1];
const int ninputs = std::min(ninput_items[0], ninput_items[1]); //just in case
float *out = (float *) output_items[0];
int size = noutput_items;
//fixme move into .h
const int pulse_offsets[4] = {0,
int(1.0 * d_samples_per_symbol),
int(3.5 * d_samples_per_symbol),
int(4.5 * d_samples_per_symbol)
};
float preamble_pulses[4];
memcpy(out, in, size * sizeof(float));
uint64_t abs_out_sample_cnt = nitems_written(0);
for(int i = d_samples_per_chip; i < size; i++) {
for(int i=0; i < ninputs; i++) {
float pulse_threshold = inavg[i] * d_threshold;
bool valid_preamble = false;
float gate_sum_now = 0, gate_sum_early = 0, gate_sum_late = 0;
if(in[i] > pulse_threshold) { //hey we got a candidate
if(in[i+1] > in[i]) continue; //wait for the peak
//check to see the rest of the pulses are there
if( in[i+pulse_offsets[1]] < pulse_threshold ) continue;
if( in[i+pulse_offsets[2]] < pulse_threshold ) continue;
if( in[i+pulse_offsets[3]] < pulse_threshold ) continue;
if(in[i] > pulse_threshold) { //if the sample is greater than the reference level by the specified amount
int gate_sum = early_late(&in[i]);
if(gate_sum != 0) continue; //if either the early gate or the late gate had greater energy, keep moving.
//the packets are so short we choose not to do any sort of closed-loop synchronization after this simple gating.
//if we get a good center sample, the drift should be negligible.
preamble_pulses[0] = in[i+pulse_offsets[0]];
preamble_pulses[1] = in[i+pulse_offsets[1]];
preamble_pulses[2] = in[i+pulse_offsets[2]];
preamble_pulses[3] = in[i+pulse_offsets[3]];
//search for the rest of the pulses at their expected positions
if( preamble_pulses[1] < pulse_threshold ) continue;
if( preamble_pulses[2] < pulse_threshold ) continue;
if( preamble_pulses[3] < pulse_threshold ) continue;
valid_preamble = true; //this gets falsified by the following statements to disqualify a preamble
float avgpeak = (preamble_pulses[0] + preamble_pulses[1] + preamble_pulses[2] + preamble_pulses[3]) / 4;
//set the threshold requirement for spaces (0 chips) to
//threshold dB below the current peak
float space_threshold = preamble_pulses[0] / d_threshold;
//search between pulses and all the way out to 8.0us to make
//sure there are no pulses inside the "0" chips. make sure
//all the samples are <= (in[peak] * d_threshold).
//so 0.5us has to be < space_threshold, as does (1.5-3), 4, (5-7.5) in order to qualify.
for(int j = 1.5 * d_samples_per_symbol; j <= 3 * d_samples_per_symbol; j+=d_samples_per_chip)
if(in[i+j] > space_threshold) valid_preamble = false;
for(int j = 5 * d_samples_per_symbol; j <= 7.5 * d_samples_per_symbol; j+=d_samples_per_chip)
if(in[i+j] > space_threshold) valid_preamble = false;
//make sure all four peaks are within 3dB of each other
float minpeak = avgpeak * 0.5;//-3db, was 0.631; //-2db
float maxpeak = avgpeak * 2.0;//3db, was 1.585; //2db
if(preamble_pulses[0] < minpeak || preamble_pulses[0] > maxpeak) continue;
if(preamble_pulses[1] < minpeak || preamble_pulses[1] > maxpeak) continue;
if(preamble_pulses[2] < minpeak || preamble_pulses[2] > maxpeak) continue;
if(preamble_pulses[3] < minpeak || preamble_pulses[3] > maxpeak) continue;
}
if(valid_preamble) {
//get a more accurate chip center by finding the energy peak across all four preamble peaks
//there's some weirdness in the early part, so i ripped it out.
bool early, late;
//get a more accurate bit center by finding the correlation peak across all four preamble bits
bool late = false;
do {
early = late = false;
//gate_sum_early= in[i+pulse_offsets[0]-1]
// + in[i+pulse_offsets[1]-1]
// + in[i+pulse_offsets[2]-1]
// + in[i+pulse_offsets[3]-1];
gate_sum_now = in[i+pulse_offsets[0]]
+ in[i+pulse_offsets[1]]
+ in[i+pulse_offsets[2]]
+ in[i+pulse_offsets[3]];
gate_sum_late = in[i+pulse_offsets[0]+1]
+ in[i+pulse_offsets[1]+1]
+ in[i+pulse_offsets[2]+1]
+ in[i+pulse_offsets[3]+1];
early = (gate_sum_early > gate_sum_now);
late = (gate_sum_late > gate_sum_now);
double now_corr = correlate_preamble(in+i, d_samples_per_chip);
double late_corr = correlate_preamble(in+i+1, d_samples_per_chip);
late = (late_corr > now_corr);
if(late) i++;
//else if(early) i--;
//if(early && late) early = late = false;
} while(late);
//finally after all this, let's post the preamble!
//now check to see that the rest of the chips in the preamble
//are below the peaks by threshold dB
float avgpeak = ( in[i+pulse_offsets[0]]
+ in[i+pulse_offsets[1]]
+ in[i+pulse_offsets[2]]
+ in[i+pulse_offsets[3]]) / 4.0;
float space_threshold = inavg[i] + (avgpeak - inavg[i])/d_threshold;
bool valid_preamble = true; //f'in c++
for( int j=1.5*d_samples_per_symbol; j<=3*d_samples_per_symbol; j++)
if(in[i+j] > space_threshold) valid_preamble = false;
for( int j=5*d_samples_per_symbol; j<=7.5*d_samples_per_symbol; j++)
if(in[i+j] > space_threshold) valid_preamble = false;
if(!valid_preamble) continue;
//be sure we've got enough room in the input buffer to copy out a whole packet
if(ninputs-i < 240*d_samples_per_chip) {
consume_each(i);
return 0;
}
//all right i'm prepared to call this a preamble
//let's integrate and dump the output
i -= d_samples_per_chip-1;
integrate_and_dump(out, &in[i], 240, d_samples_per_chip);
// out[0] = 1.0; //for debug
// out[1] = out[2] = out[3] = out[4] = avgpeak;
// out[5] = out[6] = out[7] = out[8] = space_threshold;
// out[9] = 0.0;
//now tag the preamble
add_item_tag(0, //stream ID
nitems_written(0)+i, //sample
d_key, //preamble_found
pmt::PMT_T, //meaningless for us
nitems_written(0), //sample
d_key, //frame_info
pmt::pmt_from_double((double) space_threshold),
d_me //block src id
);
//produce only one output per work call
consume_each(i+240*d_samples_per_chip);
return 240; //fixme debug should be 240
}
}
return size;
//didn't get anything this time
consume_each(ninputs);
return 0;
}

View File

@ -23,7 +23,7 @@
#ifndef INCLUDED_AIR_MODES_PREAMBLE_H
#define INCLUDED_AIR_MODES_PREAMBLE_H
#include <gr_sync_block.h>
#include <gr_block.h>
class air_modes_preamble;
typedef boost::shared_ptr<air_modes_preamble> air_modes_preamble_sptr;
@ -34,7 +34,7 @@ air_modes_preamble_sptr air_make_modes_preamble(int channel_rate, float threshol
* \brief mode select preamble detection
* \ingroup block
*/
class air_modes_preamble : public gr_sync_block
class air_modes_preamble : public gr_block
{
private:
friend air_modes_preamble_sptr air_make_modes_preamble(int channel_rate, float threshold_db);
@ -50,7 +50,8 @@ private:
pmt::pmt_t d_me, d_key;
public:
int work (int noutput_items,
int general_work (int noutput_items,
gr_vector_int &ninput_items,
gr_vector_const_void_star &input_items,
gr_vector_void_star &output_items);
};

View File

@ -31,6 +31,7 @@
#include <iomanip>
#include <modes_parity.h>
#include <gr_tag_info.h>
#include <iostream>
extern "C"
{
@ -50,7 +51,7 @@ air_modes_slicer::air_modes_slicer(int channel_rate, gr_msg_queue_sptr queue) :
{
//initialize private data here
d_chip_rate = 2000000; //2Mchips per second
d_samples_per_chip = channel_rate / d_chip_rate; //must be integer number of samples per chip to work
d_samples_per_chip = 2;//FIXME this is constant now channel_rate / d_chip_rate;
d_samples_per_symbol = d_samples_per_chip * 2;
d_check_width = 120 * d_samples_per_symbol; //how far you will have to look ahead
d_queue = queue;
@ -66,6 +67,44 @@ static bool pmtcompare(pmt::pmt_t x, pmt::pmt_t y)
t_y = pmt::pmt_to_uint64(pmt::pmt_tuple_ref(y, 0));
return t_x < t_y;
}
//this slicer is courtesy of Lincoln Labs. supposedly it is more resistant to mode A/C FRUIT.
//see http://adsb.tc.faa.gov/WG3_Meetings/Meeting8/Squitter-Lon.pdf
static bool slicer(const float bit0, const float bit1, const float ref) {
bool slice, confidence;
//3dB limits for bit slicing and confidence measurement
float highlimit=ref*2;
float lowlimit=ref*0.5;
bool firstchip_inref = ((bit0 > lowlimit) && (bit0 < highlimit));
bool secondchip_inref = ((bit1 > lowlimit) && (bit1 < highlimit));
if(firstchip_inref && !secondchip_inref) {
slice = 1;
confidence = 1;
}
else if(secondchip_inref && !firstchip_inref) {
slice = 0;
confidence = 1;
}
else if(firstchip_inref && secondchip_inref) {
slice = bit0 > bit1;
confidence = 0;
}
else if(!firstchip_inref && !secondchip_inref) { //in this case, we determine the bit by whichever is larger, and we determine high confidence if the low chip is 6dB below reference.
slice = bit0 > bit1;
if(slice) {
if(bit1 < lowlimit * 0.5) confidence = 1;
else confidence = 0;
} else {
if(bit0 < lowlimit * 0.5) confidence = 1;
else confidence = 0;
}
}
return slice;
}
/*
static double pmt_to_timestamp(pmt::pmt_t tstamp, uint64_t sample_cnt, double secs_per_sample) {
double frac;
@ -85,92 +124,63 @@ int air_modes_slicer::work(int noutput_items,
gr_vector_const_void_star &input_items,
gr_vector_void_star &output_items)
{
//do things!
const float *inraw = (const float *) input_items[0];
const float *in = (const float *) input_items[0];
int size = noutput_items - d_check_width; //since it's a sync block, i assume that it runs with ninput_items = noutput_items
int i;
std::vector<pmt::pmt_t> tags;
uint64_t abs_sample_cnt = nitems_read(0);
get_tags_in_range(tags, 0, abs_sample_cnt, abs_sample_cnt + size, pmt::pmt_string_to_symbol("frame_info"));
get_tags_in_range(tags, 0, abs_sample_cnt, abs_sample_cnt + size, pmt::pmt_string_to_symbol("preamble_found"));
std::vector<pmt::pmt_t>::iterator tag_iter;
for(tag_iter = tags.begin(); tag_iter != tags.end(); tag_iter++) {
uint64_t i = gr_tags::get_nitems(*tag_iter) - abs_sample_cnt;
modes_packet rx_packet;
framer_packet_type packet_type = framer_packet_type(pmt::pmt_to_long(gr_tags::get_value(*tag_iter)));
int packet_length;
packet_length = packet_type == framer_packet_type(Short_Packet) ? 56 : 112;
rx_packet.type = packet_type;
memset(&rx_packet.data, 0x00, 14 * sizeof(unsigned char));
memset(&rx_packet.lowconfbits, 0x00, 24 * sizeof(unsigned char));
rx_packet.numlowconf = 0;
//let's use the preamble marker to get a reference level for the packet
rx_packet.reference_level = (inraw[i]
+ inraw[i+int(1.0*d_samples_per_symbol)]
+ inraw[i+int(3.5*d_samples_per_symbol)]
+ inraw[i+int(4.5*d_samples_per_symbol)]) / 4;
//let's use the preamble to get a reference level for the packet
//fixme: a better thing to do is create a bi-level avg 1 and avg 0
//through simple statistics, then take the median for your slice level
//this won't improve decoding but will improve confidence
rx_packet.reference_level = (in[i]
+ in[i+2]
+ in[i+7]
+ in[i+9]) / 4.0;
i += 8 * d_samples_per_symbol; //move to the center of the first bit of the data
i += 16; //move on up to the first bit of the packet data
//now let's slice the header so we can determine if it's a short pkt or a long pkt
unsigned char pkt_hdr = 0;
for(int j=0; j < 5; j++) {
bool slice = slicer(in[i+j*2], in[i+j*2+1], rx_packet.reference_level);
if(slice) pkt_hdr += 1 << (4-j);
}
//std::cout << "SLICER: TYPE " << int(pkt_hdr) << std::endl;
//here we calculate the total energy contained in each chip of the symbol
if(pkt_hdr == 17) rx_packet.type = Long_Packet;
else rx_packet.type = Short_Packet;
int packet_length;
packet_length = (rx_packet.type == framer_packet_type(Short_Packet)) ? 56 : 112;
//it's slice time!
//TODO: don't repeat your work here, you already have the first 5 bits
for(int j = 0; j < packet_length; j++) {
int firstchip = i+j*d_samples_per_symbol;
int secondchip = firstchip + d_samples_per_chip;
bool slice, confidence;
float firstchip_energy=0, secondchip_energy=0;
bool slice = slicer(in[i+j*2], in[i+j*2+1], rx_packet.reference_level);
firstchip_energy = inraw[firstchip];
secondchip_energy = inraw[secondchip];
//3dB limits for bit slicing and confidence measurement
float highlimit=rx_packet.reference_level*2;
float lowlimit=rx_packet.reference_level*0.5;
bool firstchip_inref = ((firstchip_energy > lowlimit) && (firstchip_energy < highlimit));
bool secondchip_inref = ((secondchip_energy > lowlimit) && (secondchip_energy < highlimit));
//these two lines for a super simple naive slicer.
// slice = firstchip_energy > secondchip_energy;
// confidence = bool(int(firstchip_inref) + int(secondchip_inref)); //one and only one chip in the reference zone
//below is the Lincoln Labs slicer. it may produce greater bit errors. supposedly it is more resistant to mode A/C FRUIT.
//see http://adsb.tc.faa.gov/WG3_Meetings/Meeting8/Squitter-Lon.pdf
if(firstchip_inref && !secondchip_inref) {
slice = 1;
confidence = 1;
}
else if(secondchip_inref && !firstchip_inref) {
slice = 0;
confidence = 1;
}
else if(firstchip_inref && secondchip_inref) {
slice = firstchip_energy > secondchip_energy;
confidence = 0;
}
else if(!firstchip_inref && !secondchip_inref) { //in this case, we determine the bit by whichever is larger, and we determine high confidence if the low chip is 6dB below reference.
slice = firstchip_energy > secondchip_energy;
if(slice) {
if(secondchip_energy < lowlimit * 0.5) confidence = 1;
else confidence = 0;
} else {
if(firstchip_energy < lowlimit * 0.5) confidence = 1;
else confidence = 0;
}
}
//put the data into the packet
if(slice) {
rx_packet.data[j/8] += 1 << (7-(j%8));
}
//put the confidence decision into the packet
if(confidence) {
// if(confidence) {
//rx_packet.confidence[j/8] += 1 << (7-(j%8));
} else {
if(rx_packet.numlowconf < 24) rx_packet.lowconfbits[rx_packet.numlowconf++] = j;
}
// } else {
// if(rx_packet.numlowconf < 24) rx_packet.lowconfbits[rx_packet.numlowconf++] = j;
// }
}
/******************** BEGIN TIMESTAMP BS ******************/

View File

@ -19,9 +19,9 @@
# Boston, MA 02110-1301, USA.
#
#my_position = [37.76225, -122.44254]
my_position = [37.76225, -122.44254]
#my_position = [37.409066,-122.077836]
my_position = None
#my_position = None
from gnuradio import gr, gru, optfir, eng_notation, blks2, air
from gnuradio import uhd
@ -92,32 +92,21 @@ class adsb_rx_block (gr.top_block):
#the DBSRX especially tends to be spur-prone; the LPF keeps out the
#spur multiple that shows up at 2MHz
# self.filtcoeffs = gr.firdes.low_pass(1, rate, 1.8e6, 200e3)
# self.filter = gr.fir_filter_fff(1, self.filtcoeffs)
#self.lpfiltcoeffs = gr.firdes.low_pass(1, rate, 1.8e6, 200e3)
#self.lpfilter = gr.fir_filter_fff(1, self.lpfiltcoeffs)
#this is an integrate-and-dump filter to act as a matched filter
#for the essentially rectangular Mode S pulses.
#if a particular Mode S transponder is using a pulse shaping filter,
#this will not be optimal.
self.filtcoeffs = list()
for i in range(int(rate/4e6)):
self.filtcoeffs.append(1.0 / (rate/4e6))
#i think downstream blocks can therefore process at 2Msps -- try this
#self.filter = gr.fir_filter_fff(int(rate/2e6), self.filtcoeffs)
self.filter = gr.fir_filter_fff(1, self.filtcoeffs)
#rate = int(2e6)
self.preamble = air.modes_preamble(rate, options.threshold)
self.framer = air.modes_framer(rate)
#self.framer = air.modes_framer(rate)
self.slicer = air.modes_slicer(rate, queue)
self.connect(self.u, self.demod, self.filter)
self.connect(self.filter, self.avg)
self.connect(self.filter, (self.preamble, 0))
self.connect(self.u, self.demod)
self.connect(self.demod, self.avg)
self.connect(self.demod, (self.preamble, 0))
self.connect(self.avg, (self.preamble, 1))
self.connect((self.preamble, 0), (self.framer, 0))
self.connect(self.framer, self.slicer)
self.connect((self.preamble, 0), (self.slicer, 0))
def tune(self, freq):
result = self.u.set_center_freq(freq, 0)