/* -*- c++ -*- */ /* * Copyright 2005,2010 Free Software Foundation, Inc. * * This file is part of GNU Radio * * GNU Radio is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3, or (at your option) * any later version. * * GNU Radio is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with GNU Radio; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, * Boston, MA 02110-1301, USA. */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #include <sys/mman.h> #include "source_s_impl.h" #include <gnuradio/io_signature.h> #include <stdio.h> #include <errno.h> #include <iostream> #include <stdexcept> // FIXME these should query some kind of user preference namespace gr { namespace comedi { static std::string default_device_name() { return "/dev/comedi0"; } // ---------------------------------------------------------------- source_s::sptr source_s::make(int sampling_freq, const std::string dev) { return gnuradio::get_initial_sptr (new source_s_impl(sampling_freq, dev)); } source_s_impl::source_s_impl(int sampling_freq, const std::string device_name) : sync_block("comedi_source_s", io_signature::make(0, 0, 0), io_signature::make(0, 0, 0)), d_sampling_freq(sampling_freq), d_device_name(device_name.empty() ? default_device_name() : device_name), d_dev(0), d_subdevice(0/*COMEDI_SUBD_AI*/), d_n_chan(1), // number of input channels d_map(0), d_buffer_size(0), d_buf_front(0), d_buf_back(0) { int aref = AREF_GROUND; int range = 0; d_dev = comedi_open(d_device_name.c_str()); if(d_dev == 0) { comedi_perror(d_device_name.c_str()); throw std::runtime_error("source_s_impl"); } unsigned int chanlist[256]; for(int i=0; i<d_n_chan; i++) { chanlist[i] = CR_PACK(i,range,aref); } comedi_cmd cmd; int ret; ret = comedi_get_cmd_generic_timed(d_dev, d_subdevice, &cmd, d_n_chan, (unsigned int)(1e9/sampling_freq)); if(ret < 0) bail("comedi_get_cmd_generic_timed", comedi_errno()); // TODO: check period_ns is not to far off sampling_freq d_buffer_size = comedi_get_buffer_size(d_dev, d_subdevice); if(d_buffer_size <= 0) bail("comedi_get_buffer_size", comedi_errno()); d_map = mmap(NULL, d_buffer_size, PROT_READ, MAP_SHARED, comedi_fileno(d_dev),0); if(d_map == MAP_FAILED) bail("mmap", errno); cmd.chanlist = chanlist; cmd.chanlist_len = d_n_chan; cmd.scan_end_arg = d_n_chan; cmd.stop_src = TRIG_NONE; cmd.stop_arg = 0; /* comedi_command_test() tests a command to see if the trigger * sources and arguments are valid for the subdevice. If a * trigger source is invalid, it will be logically ANDed with * valid values (trigger sources are actually bitmasks), which * may or may not result in a valid trigger source. If an * argument is invalid, it will be adjusted to the nearest valid * value. In this way, for many commands, you can test it * multiple times until it passes. Typically, if you can't get * a valid command in two tests, the original command wasn't * specified very well. */ ret = comedi_command_test(d_dev,&cmd); if(ret < 0) bail("comedi_command_test", comedi_errno()); ret = comedi_command_test(d_dev,&cmd); if(ret < 0) bail("comedi_command_test", comedi_errno()); /* start the command */ ret = comedi_command(d_dev,&cmd); if(ret < 0) bail("comedi_command", comedi_errno()); set_output_multiple(d_n_chan*sizeof(sampl_t)); assert(sizeof(sampl_t) == sizeof(short)); set_output_signature(io_signature::make(1, 1, sizeof(sampl_t))); } bool source_s_impl::check_topology(int ninputs, int noutputs) { if(noutputs > d_n_chan) throw std::runtime_error("source_s_impl"); return true; } source_s_impl::~source_s_impl() { if(d_map) { munmap(d_map, d_buffer_size); d_map = 0; } comedi_close(d_dev); } int source_s_impl::work(int noutput_items, gr_vector_const_void_star &input_items, gr_vector_void_star &output_items) { int ret; int work_left = noutput_items * sizeof(sampl_t) * d_n_chan; sampl_t *pbuf = (sampl_t*)d_map; do { do { ret = comedi_get_buffer_contents(d_dev, d_subdevice); if(ret < 0) bail("comedi_get_buffer_contents", comedi_errno()); assert(ret % sizeof(sampl_t) == 0); assert(work_left % sizeof(sampl_t) == 0); ret = std::min(ret, work_left); d_buf_front += ret; assert(d_buffer_size%d_n_chan == 0); if(d_buf_front-d_buf_back > (unsigned)d_buffer_size) { d_buf_front+=d_buffer_size; d_buf_back +=d_buffer_size; } if(d_buf_front==d_buf_back) { usleep(1000000*std::min(work_left, d_buffer_size/2)/(d_sampling_freq*sizeof(sampl_t)*d_n_chan)); continue; } } while(d_buf_front == d_buf_back); for(unsigned i=d_buf_back/sizeof(sampl_t); i < d_buf_front/sizeof(sampl_t); i++) { int chan = i%d_n_chan; int o_idx = noutput_items-work_left/d_n_chan/sizeof(sampl_t) + \ (i-d_buf_back/sizeof(sampl_t))/d_n_chan; if(output_items[chan]) ((short*)(output_items[chan]))[o_idx] = (int)pbuf[i%(d_buffer_size/sizeof(sampl_t))] - 32767; } ret = comedi_mark_buffer_read(d_dev,d_subdevice, d_buf_front-d_buf_back); if(ret < 0) bail("comedi_mark_buffer_read", comedi_errno()); work_left -= d_buf_front-d_buf_back; d_buf_back = d_buf_front; } while(work_left > 0); return noutput_items; } void source_s_impl::output_error_msg(const char *msg, int err) { fprintf(stderr, "source_s_impl[%s]: %s: %s\n", d_device_name.c_str(), msg, comedi_strerror(err)); } void source_s_impl::bail(const char *msg, int err) throw (std::runtime_error) { output_error_msg(msg, err); throw std::runtime_error("source_s_impl"); } } /* namespace comedi */ } /* namespace gr */