Skip to content

Commit

Permalink
WIP: improve gnuradio block to handle multi-byte sampling better
Browse files Browse the repository at this point in the history
  • Loading branch information
ktemkin committed Nov 7, 2019
1 parent 99aae62 commit 505cbbe
Show file tree
Hide file tree
Showing 5 changed files with 179 additions and 8 deletions.
96 changes: 96 additions & 0 deletions firmware/greatfet_usb/classes/adc.c
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@
// FIXME: replace with libgreat driver
#include <libopencm3/lpc43xx/adc.h>

#include "../usb_streaming.h"


#define CLASS_NUMBER_SELF (0x111)

Expand Down Expand Up @@ -51,6 +53,50 @@ static void set_up_onboard_adc(bool use_adc1, uint32_t pin_mask, uint8_t signifi
}


static uint16_t take_adc_sample(uint8_t adc_number, uint8_t pin_channel_number)
{
const uint32_t adc_sample_mask = 0x03FF;

volatile uint32_t *adc_cr;
volatile uint32_t *adc_gdr;

// Sanity checks.
if (adc_number == 0) {
adc_cr = &ADC0_CR;
adc_gdr = &ADC0_GDR;
}
else if (adc_number == 1) {
adc_cr = &ADC1_CR;
adc_gdr = &ADC1_GDR;
} else {
pr_error("adc: invalid adc number %" PRIu8 " provided (must be <= 1)!\n", adc_number);
return EINVAL;
}

if (pin_channel_number > 8) {
pr_error("adc: error: invalid pin number %" PRIu8 " provided (must be <= 8)!\n", pin_channel_number);
return EINVAL;
}


uint32_t time_base = get_time();

// Start a conversion.
*adc_cr |= ADC_CR_START(1);

// And wait for it to complete.
while(!(*adc_gdr & ADC_DR_DONE) || (((*adc_gdr >> 24) & 0x7) != pin_channel_number)) {
if (get_time_since(time_base) > 500) {
return ETIMEDOUT;
}
}

// Read the most recently sampled V_REF from the global data register.
return (*adc_gdr >> 6) & adc_sample_mask;

}



static int verb_read_samples(struct command_transaction *trans)
{
Expand Down Expand Up @@ -116,6 +162,46 @@ static int verb_read_samples(struct command_transaction *trans)
}


/**
* Callback that captures ADC data periodically.
*/
void stream_adc_data(void *argument)
{
(void)argument;

// TODO: accept channels other than ADC0_0 and implement pinmuxing support
uint16_t sample = take_adc_sample(0, 0);
usb_streaming_send_data(&sample, sizeof(uint16_t));
}



static int verb_stream_periodic_read(struct command_transaction *trans)
{
uint32_t frequency = comms_argument_parse_uint32_t(trans);

if (!comms_transaction_okay(trans)) {
return EBADMSG;
}

// Schedule our periodic read.
usb_streaming_start_periodic_data_gathering(frequency, stream_adc_data, NULL);
comms_response_add_uint8_t(trans, USB_STREAMING_IN_ADDRESS);

return 0;
}



static int verb_stop_periodic_read(struct command_transaction *trans)
{
(void)trans;

usb_streaming_stop_periodic_gathering();
return 0;
}


/**
* Verbs for the ADC API.
*/
Expand All @@ -133,6 +219,16 @@ static struct comms_verb _verbs[] = {
" sample_count -- number of samples to read\n"
"Returns: the raw samples read from the ADC."
},

// Functionality for streaming repeated sampes over a bulk pipe.
{ .name = "stream_periodic_read", .handler = verb_stream_periodic_read,
.in_signature = "<I", .out_signature = "<B",
.in_param_names = "frequency", .out_param_names = "pipe_id",
.doc = "Schedule a periodic ADC read, and stream its results to the host." },
{ .name = "stop_periodic_read", .handler = verb_stop_periodic_read,
.in_signature = "", .out_signature = "",
.doc = "Stop any active periodic read."},

{} // Sentinel
};
COMMS_DEFINE_SIMPLE_CLASS(adc, CLASS_NUMBER_SELF, "adc", _verbs,
Expand Down
5 changes: 4 additions & 1 deletion firmware/greatfet_usb/classes/i2c.c
Original file line number Diff line number Diff line change
Expand Up @@ -232,7 +232,10 @@ static int i2c_verb_stream_periodic_read(struct command_transaction *trans)

static int i2c_verb_stop_periodic_read(struct command_transaction *trans)
{
(void)trans;

usb_streaming_stop_periodic_gathering();
return 0;
}


Expand Down Expand Up @@ -285,7 +288,7 @@ static struct comms_verb _verbs[] = {
{ .name = "stream_periodic_read", .handler = i2c_verb_stream_periodic_read,
.in_signature = "<IBB*X", .out_signature = "<B",
.in_param_names = "frequency, address, read_length, write_data", .out_param_names = "pipe_id",
.doc = "Schedule a periodic SPI transaction, and stream its results to the host." },
.doc = "Schedule a periodic I2C transaction, and stream its results to the host." },
{ .name = "stop_periodic_read", .handler = i2c_verb_stop_periodic_read,
.in_signature = "", .out_signature = "",
.doc = "Stop any active periodic read."},
Expand Down
79 changes: 73 additions & 6 deletions host/greatfet/gnuradio/block.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,24 @@ class GreatFETStreamingSource(gr.sync_block):
# The name for the block. Should be overridden by derived classes.
BLOCK_NAME='GreatFET Source'

# Default to having a uint8_t type for
OUT_SIGNATURE=[np.uint8]
# Default to having a float type for our output, as this is easiest
# to work with in GNURadio.
OUTPUT_TYPE=np.float32

# If provided, the default sample processing will normalize a sample with a given
# value to 1.0. If None, the sample will be left as is.
OUTPUT_MAX_SCALE=None

# The default timeout for the work function's USB transactions. Usually does not need
# to be overridden.
WORK_TIMEOUT=100

# Default sample size and endianness.
# Subclasses can either override this function or override the
SAMPLE_SIZE_BYTES = 1
SAMPLE_ENDIANNESS = 'little'


def __init__(self, sample_rate, *args, **kwargs):
"""
Basic block initialization for GreatFET blocks.
Expand All @@ -31,15 +42,15 @@ def __init__(self, sample_rate, *args, **kwargs):
self,
name=self.BLOCK_NAME,
in_sig=None,
out_sig=self.OUT_SIGNATURE,
out_sig=[self.OUTPUT_TYPE],
)


self.sample_rate = sample_rate

# Create our core USB transaction buffer.
# This will go away as soon as we switch to python-libusb1.
self.buffer = array.array('B', bytes(4096))
self.buffer = array.array('B', bytes(4096))

# Create an array object that will store any pending samples received from the GreatFET.
# This should probably be made a bytearray once we switch to python-libusb1.
Expand Down Expand Up @@ -79,15 +90,68 @@ def tear_down_streaming(self):
pass


def get_sample_size(self):
"""
Returns the size of a normal sample, in bytes. This default implementation returns SAMPLE_SIZE_BYTES,
but blocks with runtime-configurable sample size should override this function.
"""
return self.SAMPLE_SIZE_BYTES


def get_sample_endianness(self):
"""
Returns the endianness of a normal sample; either 'big' or 'little. This default implementation
returns SAMPLE_ENDIANNESS, but blocks with runtime-configurable sample endianness should override this function.
"""
return self.SAMPLE_ENDIANNESS


def process_samples(self, samples):
"""
Function that processes incoming samples into a format acceptable to GNURadio.
By default, processes samples into integers based on their size in bytes.
"""

# Convert our samples into a mutable array of bytes.
samples = bytearray(samples)

sample_size = self.get_sample_size()
endianness = self.get_sample_endianness()

# Figure out how many entries we'll have in our new array
new_array_size = len(samples) // sample_size

# Create a new array of samples...
new_samples = np.array([0] * new_array_size, dtype=self.OUTPUT_TYPE)

# ... and populate it.
sample_index = 0
while samples:
raw_sample = samples[0:sample_size]
del samples[0:sample_size]

# Process our sample...
sample = int.from_bytes(raw_sample, byteorder=endianness)

if self.OUTPUT_MAX_SCALE:
sample = sample // self.OUTPUT_MAX_SCALE

# ... and move to the next sample.
new_samples[sample_index] = sample
sample_index += 1

return new_samples


def work(self, input_items, output_items):
""" Core work function for our streaming blocks. """

out = output_items[0]

# Try to read the what data we can from the GreatFET's streaming pipe.
# TODO: upgrade this to use python-libusb1 and the new comms API
num_samples = self.gf.comms.device.read(self.pipe_id, self.buffer, self.WORK_TIMEOUT)
samples = self.buffer[0:num_samples]
num_sample_bytes = self.gf.comms.device.read(self.pipe_id, self.buffer, self.WORK_TIMEOUT)
samples = self.buffer[0:num_sample_bytes]

# If we have samples left over from last time, use them.
if self.pending_samples:
Expand All @@ -105,6 +169,9 @@ def work(self, input_items, output_items):
self.pending_samples = samples[len(out):]
samples = samples[:len(out)]

# Perform any sample processing we need to do before sending these samples upstream.
samples = self.process_samples(samples)

# Finally, pass our samples to GNURadio.
out[:len(samples)] = samples
return len(samples)
Expand Down
5 changes: 5 additions & 0 deletions host/greatfet/gnuradio/i2c.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ class I2CSourceBlock(GreatFETStreamingSource):


def set_up_streaming(self, address, data_to_write, read_length, prelude_script=''):
self.sample_size_bytes = read_length

# If we were provided with a 'prelude script', run it before we execute our main block.
if prelude_script:
Expand All @@ -31,3 +32,7 @@ def set_up_streaming(self, address, data_to_write, read_length, prelude_script='

def tear_down_streaming(self):
self.gf.apis.i2c.stop_periodic_read()


def get_sample_size(self):
return self.sample_size_bytes
2 changes: 1 addition & 1 deletion host/greatfet/gnuradio/i2c_register_source.block.yml
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ templates:

outputs:
- domain: stream
dtype: byte
dtype: float
multiplicity: 1

file_format: 1

0 comments on commit 505cbbe

Please sign in to comment.